I'm having some issues validating the WebHook that Xero sends to Salesforce...

The body of the WebHook sent from Xero must be encrypted using HMAC-SHA0-256, and the response Status Code returned back to Xero must be 200 if success and 401 if failure. Xero will NOT allow you to receive WebHooks until they verify that your endpoint is accepting the correct body and returning the correct response back.

So I used the following:

RestContext.request.headers.get('x-xero-signature') == EncodingUtil.base64Encode(

The Label.Xero_WebHook_Key is a Key that is given to us from Xero to use when generating the hash.

The x-xero-signature header is the already hashed body and it must match the hash that we generate in Apex. But no matter what I have tried I cannot seem to get this to work and return the correct response.

Has anyone managed to get this working? I got this working in Node.js, but can't for the life of me see any issues or reasons why it isn't working in Apex :/

Node.js example;

var hmac = crypto.createHmac("sha256", xeroConfig.webhookKey).update(req.body.toString()).digest("base64");
  • Are you sure it needs to be a HMAC and not Hash?
    – Robs
    Jun 22, 2018 at 11:32
  • developer.xero.com/documentation/webhooks/… On there is says: If the payload is hashed using HMACSHA256 with your webhook signing key and base64 encoded, it should match the signature in the header.
    – Tim
    Jun 22, 2018 at 11:37

1 Answer 1


Better late than never but I recently have webhooks up and running in Salesforce. You effectively need to:

  1. Sign the payload received from Xero using HMACSHA256 and using the key provided in the Webhook settings
  2. Base64 encode the signed payload
  3. Verify that the result from #2 matches what Xero passes in in the x-xero-signature header

Here's my sample code:

@RestResource (urlMapping='/xero/webhook')
global class XeroWebhook {

    global static void processIntentToReceive () {

        // Retrieve the Xero signature from the headers
        String xeroSignature = RestContext.request.headers.get('x-xero-signature');

        // Retrieve the Xero payload body
        String xeroPayload = RestContext.request.requestBody.toString();

        // We need to validate that the Xero Payload is hashed using HMACSHA256 and the provided key, and base64 encoded
        // This encoded payload needs to match what was passed in the header from Xero.
        // Full details: 
        // https://developer.xero.com/documentation/webhooks/configuring-your-server

        // Verify the signature using 'hmacSHA256'. I have the Webhook key stored in a Custom Setting
        Blob signedPayload = Crypto.generateMac('hmacSHA256', Blob.valueOf(xeroPayload), Blob.valueOf(Xero_Config__c.getInstance().Webhook_Key__c));

        // Once we have the signed payload, encode it using base64 to convert back to a string
        String encodedPayload = EncodingUtil.base64Encode(signedPayload);

        // Return status code based on whether signed payload matches or not
        RestContext.response.statusCode = encodedPayload == xeroSignature ? 200 : 401;

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .