function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Chad VanHornChad VanHorn 

HttpRequest Callout to Azure Message Queue REST API

I'm trying to drop messages on an Azure message queue for processing.  I have been able to build the HttpRequest, but am receiving a 403 - failed to authenticate message from Azure when calling the REST API.  I am using the following code:

string storageKey = 'removedforprivacy';

    Datetime dt = Datetime.now();
    string formattedDate = dt.formatGMT('EEE, dd MMM yyyy HH:mm:ss') + ' UTC';
    string stringToSign = 'POST\n\napplication/xml\n\nx-ms-date:' + formattedDate + '\n' +
                                      '/myqueue/testqueue/messages';

    // Sign the request
    Blob temp = EncodingUtil.base64Decode(storageKey);
    Blob hmac = Crypto.generateMac('HMacSHA256', Blob.valueOf(stringToSign), temp);
    string signature = EncodingUtil.base64Encode(hmac);
    Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))));

    // This ends up being the exact same as the console app
    system.debug('SIGNATURE==>SharedKey myqueue:' + signature);

    HttpRequest req = new HttpRequest();
    req.setMethod('POST');
    req.setHeader('content-type', 'application/xml');
    req.setHeader('x-ms-date', formattedDate);
    string authHeader = 'SharedKey myqueue:' + signature;
    req.setHeader('Authorization', authHeader);

    req.setEndpoint('https://myqueue.queue.core.windows.net/testqueue/messages');

    req.setBody('<QueueMessage><MessageText>' + EncodingUtil.base64Encode(Blob.valueOf('This is a test from salesforce')) + '</MessageText></QueueMessage>');

    system.debug(req);

    Http http = new Http();

    try
    {
        HTTPResponse res = http.send(req);

        system.debug(res.toString());
        system.debug(res.getStatus());
        system.debug(res.getStatusCode());

    }
    catch (system.CalloutException ce)
    {
        system.debug(ce);
    }
I created a small .NET console app to verify how to create the request and the connectivity.  Once that was working, I verified the signed signature used in the Authorization header is the same when created both in .NET and in Salesforce.  Has anyone else came across this?  Is there something you can see in the code I have written?  Is there a way for me to capture the HttpRequest that is being posted by Salesforce (something similar to fiddler would be great)?
Best Answer chosen by Chad VanHorn
Chad VanHornChad VanHorn
Finally figured out the issue after building the HTTP request manually outside of SF1.  I apparently missed in the documentation that the datetime needs to be in GMT not UTC.  Solving that allows the code to drop messages on the queue as expected.

All Answers

shiv@SFDCshiv@SFDC
I think you are doing making mistake in creating signature. Can you please tell me which azurae service you are using ?
Chad VanHornChad VanHorn
I am using the Azure Storage queue.  The API runs under https://*.queue.core.windows.net/testqueue/messages where * is the storage account name.  Is this what you were referring to or were asking for the specific storage account I am using?
Chad VanHornChad VanHorn
Finally figured out the issue after building the HTTP request manually outside of SF1.  I apparently missed in the documentation that the datetime needs to be in GMT not UTC.  Solving that allows the code to drop messages on the queue as expected.
This was selected as the best answer
Manuel Tejeiro Del RíoManuel Tejeiro Del Río
Hi Chad,

I'm trying something similar but for peeking a message. However, I'm getting an error signing the request. Your line #12:  Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)))); is not ok. How do you manage to sign it ? 
Thank you very much!
Chad VanHorn 1Chad VanHorn 1
here is the class I ended up with.  it is running in our production org and has been for a few years now.  feel free to use it but I offer no warranty.
 
public class AzureManagement {
    private string storageKey;
    private string storageName;
    private string storageContainer;
    private string storageUrl;
    private Integer batchSize;
    
    private final string DATEFORMAT = 'EEE, dd MMM yyyy HH:mm:ss z';
    private final string MESSAGEFORMAT = '<QueueMessage><MessageText>{0}</MessageText></QueueMessage>';
    
    public AzureManagement(string key, string name, string container, string url)
    {
        this(key, name, container, url, 100);
    }
    
    public AzureManagement(string key, string name, string container, string url, Integer batch)
    {
        System.debug(LoggingLevel.INFO, 'AzureManagement setting up...');
        System.debug(LoggingLevel.INFO, 'storageKey = ' + key);
        System.debug(LoggingLevel.INFO, 'storageName = ' + name);
        System.debug(LoggingLevel.INFO, 'storageContainer = ' + container);
        System.debug(LoggingLevel.INFO, 'storageUrl = ' + url);
        System.debug(LoggingLevel.INFO, 'batchSize = ' + batch);

        storageKey = key;
        storageName = name;
        storageContainer = container;
        storageUrl = url;
        batchSize = batch;
    }
    
    public boolean sendMessageToQueue(string message)
    {
        boolean success = true;
        
        System.debug(LoggingLevel.INFO, 'Sending message to queue: ' + message);

        try
        {
            sendMessage(MESSAGEFORMAT.replace('{0}', EncodingUtil.base64Encode(Blob.valueOf(message))));
        }
        catch (system.CalloutException ce)
        {
            System.debug(LoggingLevel.ERROR, ce);
            success = false;
        } 
        
        return success;
    }
    
    public boolean sendMultipleMessagesToQueue(List<string> messages)
    {
        boolean success = true;
        
        Integer passes = messages.size() / batchSize;
        if ((Math.mod(messages.size(), batchSize)) > 0 )
        {
            passes++;
        }
        system.debug(messages.size());
        system.debug(passes);
        for (Integer p = 0; p < passes; p++)
        {
            string finalMessage = '{ command: "batch", list: [';
            
            for (Integer i = 0; (i < batchSize) && (i + (batchSize * p) < messages.size()); i++)
            {
                finalMessage += messages.get(i + (batchSize * p));
                if (i < (batchSize - 1))
                {
                    finalMessage += ', ';
                }
            }
            
            finalMessage += '] }';
            
            try
            {
                sendMessage(MESSAGEFORMAT.replace('{0}', EncodingUtil.base64Encode(Blob.valueOf(finalMessage))));
                
            }
            catch (system.CalloutException ce)
            {
                system.debug(ce);
                success = false;
            }
        }
        
        return success;
    }
    
    private void sendMessage(string message)
    {        
        // Create HTTP POST request
        HttpRequest req = new HttpRequest();
        req.setMethod('POST');
        req.setHeader('content-type', 'application/xml');
        
        string formattedDate = getFormattedDate();
        req.setHeader('x-ms-date', formattedDate);
        
        // Create authorization header per Azure rules
        string signature = getSignatureForQueue(formattedDate);
        string authHeader = 'SharedKey ' + storageName + ':' + signature;
        req.setHeader('Authorization', authHeader);
        
        req.setEndpoint(storageUrl);
        
        // Add message to body
        req.setBody(message);
                
        Http http = new Http();
        HTTPResponse res;
        
        // only do this if not running in a test method
        if(!Test.isRunningTest())
        {
            System.debug(LoggingLevel.INFO, 'Sending the message to Azure');
            res = http.send(req);                
            System.debug(LoggingLevel.INFO, 'http.send result status: ' + res.getStatus());
        }
        else
        {
            System.debug(LoggingLevel.INFO, 'Running in a test so not sending the message to Azure');
        }
    }
    
    private string getSignatureForQueue(string formattedDate)
    {
        // build signature string based on Azure documentation
        string stringToSign = 'POST\n\napplication/xml\n\nx-ms-date:' + formattedDate + '\n/' + storageName + '/' + storageContainer + '/messages';
    
        // create signature by encoding and then encrypting string and then encoding again
        Blob temp = EncodingUtil.base64Decode(storageKey);
        Blob hmac = Crypto.generateMac('HMacSHA256', Blob.valueOf(stringToSign), temp);
        
        return EncodingUtil.base64Encode(hmac);
    }
    
    private string getFormattedDate()
    {
        // Format as such: Sun, 11 Oct 2009 19:52:39 GMT
        Datetime dt = Datetime.now().addMinutes(-5);
        return dt.formatGMT(DATEFORMAT);
    }
}

Hope it helps.
Manuel Tejeiro Del RíoManuel Tejeiro Del Río
OMG, you're awesome. Finally I have managed to get the message from the queue, I think the problem was in my URI... something really weird.. I'll post my code when it's done but what you have provided is simply amazing. Thank you buddy for your prompt response and your code :) 

Thank you so much, I believe it will help many people!
Manish Chandra 5Manish Chandra 5
Hi Chad,

First Of all I'll thank you for the solution you have provided for sending messages to Azure queue. I have also not exactly but very similar requirement, I have to fetch blobs from Azure Storage and I have follwed your path only but still I amm facing same issue that you're facing. I am stuck here from past few days. Can you please help me? Here is my code.

public class AzurePOC {
    
        public void azurePOCMethod(){
        
            string storageKey = 'Z7yhAnPUt8iYXSkMK3lvHqQEDS92kR2h5hbZWn6oAjTFrpcsDhEbobcuBxpVtTyHcCTZfds1UnrtUtIESMGJBQ==';
        
            Datetime dt = Datetime.now().addMinutes(-5);
           
            string formattedDate = dt.formatGMT('EEE, dd MMM yyyy HH:mm:ss z');
            //string stringToSign = 'GET\n\napplication/xml\n\n\n' +'/testcontainer/2016';
            //string stringToSign = 'GET\n\napplication/xml\n\nx-ms-date:' + formattedDate + '\n/testaccount5678/bill';
            string stringToSign = 'GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:'+formattedDate+'\nx-ms-version:2009-09-19\n/testaccount5678/bill\ncomp:metadata\nrestype:container\ntimeout:20';
        
            // Sign the request
            Blob temp = EncodingUtil.base64Decode(storageKey);
            Blob hmac = Crypto.generateMac('HMacSHA256', Blob.valueOf(stringToSign), temp);
            string signature = EncodingUtil.base64Encode(hmac);
          
        
            system.debug('SIGNATURE==>SharedKey :' + signature);
            system.debug('formattedDate:'+formattedDate);
            HttpRequest req = new HttpRequest();
            req.setMethod('GET');
           req.setHeader('content-type', 'application/xml');
            req.setHeader('x-ms-date', formattedDate);
            string authHeader = 'SharedKey testaccount5678:' + signature;
            req.setHeader('Authorization', authHeader);
            req.setHeader('x-ms-version', '2009-09-19');
            //req.setHeader('Content-Length', '0');
       
            req.setEndpoint('https://testaccount5678.blob.core.windows.net/bill/test567');

            system.debug(req);
        
            Http http = new Http();
        
            try
            {
                HTTPResponse res = http.send(req);
        
                system.debug(res.toString());
                system.debug(res.getStatus());
                system.debug(res.getStatusCode());
        
            }
            catch (system.CalloutException ce)
            {
                system.debug(ce);
            }
    }
}

 
Manish Chandra 5Manish Chandra 5
I am facing this error, in debug log.

18:28:17.50 (645456782)|USER_DEBUG|[44]|DEBUG|System.HttpResponse[Status=Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature., StatusCode=403] 18:28:17.50 (645510084)|USER_DEBUG|[45]|DEBUG|Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. 18:28:17.50 (645545530)|USER_DEBUG|[46]|DEBUG|403
bakul.patelbakul.patel
I am able to post message (got response 201 - created, which I guess is for success). But when I login into my Azure account, I don't see any message on the queue. Does that mean the message did not create?
bakul.patelbakul.patel
It seems 201 is not success response, becase when I print response body it is empty. Any thoughts?
I am using exact same code posted above by Chad