Skip to main content

Webhook authentication

The webhook notification is an HTTP POST request sent to your web server. Your server must listen for it and then authenticate it with HMAC before you can process the message.

Verify the request by using the unique secret provided when the webhook was registered along with the Host, X-ms-date, X-ms-content-sha256 and Authorization headers of the request.

The authorization is an SHA-256 HMAC hash of the following and is created using the secret specified for the webhook:

  • Request date
  • Content of the webhook payload
  • Webhook URI
How HMAC works
  1. Secret Key: A secret key is shared between the sender and the receiver.
  2. Message: The message to be authenticated.
  3. Hash Function: A cryptographic hash function (e.g., SHA-256) is used.
  4. HMAC Generation:
    • The message and the secret key are combined in a specific way.
    • The combined data is hashed using the cryptographic hash function.
    • The result is the HMAC value.
  5. Verification:
    • The receiver uses the same secret key and hash function to generate an HMAC value for the received message.
    • The receiver compares the generated HMAC value with the HMAC value sent with the message.
    • If they match, the message is verified as authentic and unaltered.

How to get the secret

When you first register for a webhook, you will receive a response including a secret.

For example:

{
"url": "https://webhook.site/e2cee29b-012e-4f1d-8ef4-e95fd74a7a63",
"events": ["epayments.payment.created.v1", ...]
}

{
"id":"0b52e5bd-be1e-42a1-acf2-e331f52c68ac",
"//": "👇 This is the important bit"
"secret":"A0+AeKBRG2KRGvnNwJpQlb6IJFk48CKXCIcrLoHncVJKDILsQSxS6NWCccwWm6r6FhGKhiHTBsG2wo/xU6FY/A=="
}

If you have lost the secret, see How can I replace a webhook?.

How to verify a received request

You will receive an HTTP post with this format:

POST https://webhook.site/e2cee29b-012e-4f1d-8ef4-e95fd74a7a63

x-ms-date = Thu, 30 Mar 2023 08:38:32 GMT
x-ms-content-sha256 = lNlsp1XA03N34HrQsVzPgJKtC+r7l/RBF4V3JQUWMj4=
Authorization = HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=agAiSyogQbDHpeucoNwYz+yAr5nJ+v+zasdkSbqzv+U=

{
"some-unique-content":"ee6e441b-cc4a-46f8-895d-a5af79bcc233/hello-world"
}

To verify the request:

  1. Check that the content has not been modified

    Hash the serialized request content using SHA256 encryption then base64 encode it. This hash should match the header value 'x-ms-content-sha256'.

    'X-Ms-Content-Sha256': 'lNlsp1XA03N34HrQsVzPgJKtC+r7l/RBF4V3JQUWMj4='
  2. Verify the authentication header

    Concatenate request method, path and query, date, host, and content hash (from previous step) in this format:

    POST\n\<pathAndQuery\>\n\<date\>;\<host\>;\<hash\>

    Please note the use of \n not \r\n.

    Sign it with the hmac-sha256 and your secret. This should match the Signature part of the 'authorization' header in the request you received.

    Authorization: HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=agAiSyogQbDHpeucoNwYz+yAr5nJ+v+zasdkSbqzv+U=

Sample Code

The following are examples of authenticating the HTTP requests.

'use strict';

const assert = require('node:assert');
const { describe, it } = require('node:test');

const crypto = require('crypto');

describe('Sample Code', () => {

it('Verifying Hmac Headers', () => {

const secret = 'A0+AeKBRG2KRGvnNwJpQlb6IJFk48CKXCIcrLoHncVJKDILsQSxS6NWCccwWm6r6FhGKhiHTBsG2wo/xU6FY/A==';

const request = {
method: 'POST',
url: 'https://webhook.site/e2cee29b-012e-4f1d-8ef4-e95fd74a7a63',
pathAndQuery: '/e2cee29b-012e-4f1d-8ef4-e95fd74a7a63', // this is the path and query part of the target URL
headers: {
'X-Ms-Date': 'Thu, 30 Mar 2023 08:38:32 GMT' ,
'X-Ms-Content-Sha256': 'lNlsp1XA03N34HrQsVzPgJKtC+r7l/RBF4V3JQUWMj4=' ,
'Host': 'webhook.site', // this is the host part of the target URL
'Authorization': 'HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=agAiSyogQbDHpeucoNwYz+yAr5nJ+v+zasdkSbqzv+U='
},
content: '{"some-unique-content":"ee6e441b-cc4a-46f8-895d-a5af79bcc233/hello-world"}'
};

// Verify content
const expectedContentHash = crypto
.createHash('sha256')
.update(request.content)
.digest('base64')

assert.equal(request.headers['X-Ms-Content-Sha256'], expectedContentHash, 'Content hash was not valid');

// Verify signature
const expectedSignedString =
`${request.method}\n` +
`${request.pathAndQuery}\n` +
`${request.headers['X-Ms-Date']};${request.headers['Host']};${request.headers['X-Ms-Content-Sha256']}`

const expectedSignature = crypto.createHmac('sha256', secret)
.update(expectedSignedString)
.digest('base64')

const expectedAuth = `HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=${expectedSignature}`

assert.equal(expectedAuth, request.headers.Authorization, "Authorization was not valid");
});
});

Help us improve our documentation

Did you find what you were looking for?