Secure webhooks

Ensure your server is only receiving the expected EverTransit requests for security reasons.

Once your server is configured to receive payloads, it'll listen for any payload sent to the endpoint you configured. For security reasons, you probably want to validate that these requests are precisely coming from EverTransit. The easiest method of doing this is to set up a webhook secret and validate the request payload.

Setting your webhook secret

You'll need to set up your secret in two places: EverTransit webhooks and on your server. Remember that previously, when we were creating a webhook, we had to enter a value in the input secret in order to create our webhook. In this way, we set the secret in the webhook. However, if you did not take note of this value, you can go to the webhook details and copy it by clicking on Show button and then copying it.

Also, if you want to change it, you can Edit the webhook and enter the new value.

Next, set up an environment variable on your server that stores this secret. Typically, this is as simple as running:

export SECRET=MY_SECRET

Or adding it to the .env file.

For the purpose of this tutorial, we will add a .env file to our NodeJS project, which will contain this variable. To achieve this, we can do the following from a terminal in our project.

echo SECRET=MY_SECRET > .env

We install dotenv to be able to load this variable when starting our Express application:

npm install dotenv

And then we add this as the first line of our index.js file:

index.js
require('dotenv').config();

This will load the SECRET variable from the .env file.

Never hardcode the token into your app!

Validating payloads from EverTransit

When your secret token is set, EverTransit uses it to create a hash signature with each payload. This hash signature is included with the headers of each request as Webhook-Signature.

For example, let's remember that our server that we had previously configured to listen for our webhooks had looked something like this:

index.js
require('dotenv').config();
const express = require('express');
const PORT = 3000;

const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
    const body = req.body;
    console.log(body);
    return res.sendStatus(200);
});

app.listen(PORT, () => {
    console.log(`Listening at port ${PORT}`);
});

The intention is to calculate a hash using your SECRET, and ensure that the result matches the hash from EverTransit. EverTransit uses an HMAC hex digest to compute the hash, so you could reconfigure your server to look something like this:

require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const PORT = 3000;

const app = express();

app.use(express.json({
    verify: (req, _res, buf) => {
        req.rawBody = buf;
    },
}));

const isAuthorized = (req, res, next) => {
    const expectedSig = req.header('Webhook-Signature');
    const hash = crypto.createHmac('sha256', process.env.SECRET)
        .update(req.rawBody)
        .digest('base64');

    const actualSig = `sha256=${hash}`;
    if(crypto.timingSafeEqual(Buffer.from(actualSig), Buffer.from(expectedSig))){
        return next();
    }

    return res.sendStatus(401);
}

app.post('/webhook', isAuthorized, (req, res) => {
    const body = req.body;
    console.log(body);
    return res.sendStatus(200);
});

app.listen(PORT, () => {
    console.log(`Listening at port ${PORT}`);
});

Your language and server implementations may differ from this sample code. However, there are a number of very important things to point out:

  • No matter which implementation you use, the hash signature starts with sha256=, using the key of your secret token and your payload body.

  • Using a plain == operator is not advised. A method like secure_compare performs a "constant time" string comparison, which helps mitigate certain timing attacks against regular equality operators.

Note: You can download the complete source code for this project from the EverTransit-webhooks-example repo.

Last updated