How to authenticate the webhook event
Work with these docs in your editor
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-toolkitclaude plugin install vipps@agent-toolkitThe 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
- Secret Key: A secret key is shared between the sender and the receiver.
- Message: The message to be authenticated.
- Hash Function: A cryptographic hash function (e.g., SHA-256) is used.
- 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.
- 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:
-
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=' -
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
\nnot\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.
- JavaScript
- .Net C#
'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");
});
});
using System.Security.Cryptography;
using System.Text;
public class SampleCode
{
public void Verifying_Hmac_Headers()
{
var secret = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
var request = new
{
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 = new Dictionary<string, string>
{
{ "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
var contentHashInBytes = SHA256.HashData(Encoding.UTF8.GetBytes(request.Content));
var expectedContentHash = Convert.ToBase64String(contentHashInBytes);
Assert.Equal(expected: expectedContentHash, actual: request.Headers["x-ms-content-sha256"]);
// Verify signature
var expectedSignedString =
$"{request.Method}\n" +
$"{request.PathAndQuery}\n" +
$"{request.Headers["x-ms-date"]};{request.Headers["Host"]};{request.Headers["x-ms-content-sha256"]}";
using var hmacSha256 = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hmacSha256Bytes = Encoding.UTF8.GetBytes(expectedSignedString);
var hmacSha256Hash = hmacSha256.ComputeHash(hmacSha256Bytes);
var expectedSignature = Convert.ToBase64String(hmacSha256Hash);
var expectedAuthorization = $"HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature={expectedSignature}";
Assert.Equal(expected: expectedAuthorization, actual: request.Headers["Authorization"]);
}
}