GuidesAPI ReferenceChangelog
Guides

How does signature verification work?

Deel uses SHA256 Webhook Signature Verification for security.

Securing a Webhook involves the verification of the Webhook source and destination and the validation of the payload (Message Content). Among different webhook authentication strategies available, signature verification stands out as the strongest form of protection for securing webhooks.

Verify signatures

Refer below image to understand the signature verification in the following steps:

3000

Source: hookdeck.com

  • A secret key is available with the Webhook Provider (Deel) and Consumer (Client). This is also known as the Webhook signing key. This key is provided when you create a Webhook subscription.
  • When sending a Webhook, Deel uses this key and an HMAC (Hash-based Message Authentication Code) hashing algorithm (SHA256) and to create a cryptographic hash of the Webhook payload. This cryptographic hash is the Webhook’s unique Signature.
  • The signature is sent in a custom header along with the Webhook request.
  • When the Webhook arrives at the Webhook URL, the receiving application takes the Webhook payload and uses the secret key along with the cryptographic algorithm to calculate the signature.
  • You need to use POST prefix with thehmac.update function in cryptographic algorithm on your side, you can check how it's implemented in an example below
  • The calculated signature is then compared with the one sent by the Deel in the custom header. If there is a match then the request is valid. If not, the Webhook is rejected.

NodeJS example

Let’s take a look at what authenticating Webhooks using signature verification looks like with NodeJS

How to launch server with verification

Requirements

  1. Node.js >= 16
  2. Webhook configured to work with local server

Steps

  1. Create 2 files main.js and package.json
  2. Copy content from the code snippet below
  3. Put you Signing key into secret const
  4. Run npm i
  5. Run npm start
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('node:crypto');

// App
const app = express();
const port = 1337;
const sigHeaderName = 'x-deel-signature';
const sigHashAlg = 'sha256';
const sigPrefix = 'POST';
const secret = 'your_webhook_signing_key';

//Get the raw body

//Validate payload
function validatePayload(req, res, next) {
    if (req.get(sigHeaderName)) {
        //Extract Signature header
        const sig = Buffer.from(req.get(sigHeaderName) || '', 'utf8')

        //Calculate HMAC
        const hmac = crypto.createHmac(sigHashAlg, secret)
        const digest = Buffer.from(hmac.update(sigPrefix + req.rawBody).digest('hex'), 'utf8');

        //Compare HMACs
        if (sig.length !== digest.length || !crypto.timingSafeEqual(digest, sig)) {
            const message = `Request body digest (${digest}) did not match ${sigHeaderName} (${sig})`;
            console.error(message);
            return res.status(401).send({
                message
            });
        } else {
            return res.status(200).send({
                message: `Webhook signature verification successful`
            });
        }
    }

    return next();
}

function post(req) {
    console.log(JSON.stringify(req.body, null, 2));
}

app.use(bodyParser.json(
    {
        verify: (req, res, buf, encoding) => {
            if (buf && buf.length) {
                req.rawBody = buf.toString(encoding || 'utf8');
            }
        },
    }
));

app.use(validatePayload);
app.post('/', post);
app.set('port', port);

app.listen(port, () => console.log(`Server running on localhost: ${port}`));
{
  "name": "validate-webhook",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node main.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "~1.20.1",
    "express": "~4.18.2"
  }
}

Once the server is running, you use below CURL after replacing placeholder values to verify the signature.

curl --location 'http://localhost:$PORT' \
--header 'x-deel-signature: <header value you received>' \
--header 'Content-Type: application/json' \
--data-raw '<data you received without any modification>'

Webhooks & Scopes

Unlike Other API endpoints, webhook subscription endpoints do not require specific scopes. Webhooks are accessible via API with a valid API token.


What’s Next