> **Description:** Security guide for validating webhook authenticity using HMAC signatures, including code examples and verification best practices.

# How to authenticate the webhook event

The webhook is an `HTTP POST` request sent to your web server (the callback URL you specified during [webhook registration](https://developer.vippsmobilepay.com/docs/APIs/webhooks-api/api-guide.md#register-a-webhook)).
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](https://developer.vippsmobilepay.com/docs/APIs/webhooks-api/api-guide.md#register-a-webhook), you will receive a response including a `secret`.

For example:

```json
{
    "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](https://developer.vippsmobilepay.com/docs/APIs/webhooks-api/faq.md#how-can-i-replace-a-webhook).

## How to verify a received request

You will receive an `HTTP POST` with this format:

```js
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'.

    ```js
    '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.

    ```HTTP
    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.

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

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

> **Full site overview:** For every page in this documentation, read [https://developer.vippsmobilepay.com/llms.txt](https://developer.vippsmobilepay.com/llms.txt).
