Skip to main content

How to authenticate the webhook event

View as Markdown (opens in a new tab)
Install AI tools

Our plugin gives your assistant up-to-date guidance for payments and login. Full instructions

Run both commands, in order.

claude plugin marketplace add vippsas/agent-toolkit
claude plugin install vipps@agent-toolkit

The webhook is an HTTP POST request sent to your web server (the callback URL you specified during webhook registration). For security, you should authenticate the message to avoid a man-in-the-middle attack.

Use HMAC to 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:

{
"id":"0b52e5bd-be1e-42a1-acf2-e331f52c68ac",
"//": "👇 This is the important bit"
"secret":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
}

If you have lost the secret, you need to replace the 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 the 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 = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==';

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");
});
});