How to Authenticate a Webhook?

How to authenticate a webhook

Once you have received the payload from the webhook, it needs to be confirmed that it did indeed come from Bracken.

We can use the header of the payload to confirm that it came from Bracken by encrypting the secret that we created with the JSON body payload, using the HMACSHA256 algorithm which produces a binary digest, then we use base64 to encode it as a string output.

Then by matching it to the already encrypted header of the payload, it means that if the two signatures match it the payload came from Bracken.

Note: This is why we suggest to use a secure secret when signing up for our webhooks because it is used to authenticate the response.

JavaScript Example Code

const express = require('express');
const app = express(); //app is an instance of express.

// library to assist with encryption
const crypto = require('crypto');
// same secret you declared when creating the webhook 
const secret = "12345";
// the front of the header will always have this.
const front = "HMACSHA256 ";

//app.METHOD(PATH, HANDLER)
app.post('/webhooks', function (req, res) {

    const hash = crypto
        .createHmac('sha256', secret)
        .update(JSON.stringify(req.body))
        .digest('base64');

    const yourSignature = front + hash;

    const brackenWebhookSignature = req.header('Authorization');

    if ((brackenWebhookSignature != null) && brackenWebhookSignature == yourSignature) {
        res.send('OK the signatures match')
    } else {
        return res.status(403).send('Do not trust, not from a Bracken webhook');
    }
});

Javascript Code Explanation

We will now receive ‘OK the signatures match’ if the webhook comes from Bracken and ‘Do not trust, not from a Bracken webhook’ if the response is not from Bracken.
In the code above we have encrypted the secret and the JSON body using the HMACSHA256 algorithm which produces a binary digest, then we use base64 to encode it as a string output.

To do this we used crypto which is a Node.js module that provides cryptographic functionality.
We declare it at the start of the file

const crypto = require('crypto'); 

and then use it to hash the secret and JSON body within the method that catches the incoming webhook responses.

We do this by creating a constant called hash using the crypto module.
First we create an Hash-Based Message Authentication Code (HMAC) specifying the HMACSHA256 algorithm to encrypt the secret.

const hash = crypto
    .createHmac('sha256', secret)

Then we update the hash to include the JSON body of the response.

    .update(JSON.stringify(req.body))

Lastly we encode the hash using base64 encoding, because need to encode the binary data that the HMACSHA256 algorithm produces and convert it to textual data, so that it is comparable to our response header.

    .digest('base64');

Now we have encrypted our secret and the JSON body from the response that we received. Next in the code we are comparing if our signature matches the Authorization header from the response we received.

    const yourSignature = front + hash;

    const brackenwebhooksignature = req.header('Authorization');

    if ((brackenwebhooksignature != null) && brackenwebhooksignature == yourSignature) {
        res.send('OK the signatures match')
    } else {
        return res.status(403).send('Do not trust, not from a Bracken webhook');
    }

If the signatures match then you have successfully authenticated that the webhook response came from Bracken.

C# Example Code

//using this to get the header payload from the response
using System.Net.Http.Headers;

public async Task<IActionResult> VerifyHook()
        {
            // declare your secret 
            string secret = "YOUR_SECRET";
            // declare the static text that will always be at the start of the response.
            string authenticationScheme = "HMACSHA256";
            //Get the header payload from the response
            AuthenticationHeaderValue authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);

            // compare the signatures
            if ((authHeader != null) && authHeader.Scheme.Equals(authenticationScheme, StringComparison.OrdinalIgnoreCase))
            {
                string signatureBase64 = authHeader.Parameter;

                if (!string.IsNullOrEmpty(signatureBase64))
                {
                    try
                    {
                        //Encode the secret
                        byte[] secretByteArray = Encoding.UTF8.GetBytes(secret);
                        byte[] content;

                        // Read request content
                        using (var ms = new MemoryStream(2048))
                        {
                            await Request.Body.CopyToAsync(ms);
                            content = ms.ToArray();
                        }

                        // Generate content hash
                        using (HMACSHA256 hmac = new HMACSHA256(secretByteArray))
                        {
                            byte[] hashBytes = hmac.ComputeHash(content);
                            string hashBase64 = Convert.ToBase64String(hashBytes);

                            if (signatureBase64.Equals(hashBase64, StringComparison.Ordinal))
                            {
                                return Ok();
                            }
                        }
                    }
                    catch (Exception)
                    {
                        return Unauthorized();
                    }
                }
            }

            return Unauthorized();
        }