Skip to main content

Request authentication

An API request from the Webhooks API can be verified by using the Host, X-ms-date, X-ms-content-sha256 and Authorization headers with the unique secret specified when the webhook was registered with POST:/webhooks/v1/webhooks. For more info, see POST:/webhooks/v1/webhooks.

The authorization is a SHA-256 HMAC hash of:

  • Request date
  • Content of the webhook payload
  • Webook URI that is created using the secret specified for the webhook.

Sample Data

Receiving the secret when registering webhook

POST https://api.vipps.no/webhooks/v1/webhooks

{
"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=="
}

Sample request sent to you with valid authorization and content

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

How to verify a received 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 previous step) like so: POST\n\<pathAndQuery\>\n\<date\>;\<host\>;\<hash\> and sign it with the hmac-sha256 and your secret. This ought to match the Signature part of the 'authorization' header in the request you received. Please note the use of \n not \r\n.

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

Sample Code

'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?