Skip to main content

Webhooks - Technical documentation

Updated over a week ago

🔀 Sequence diagram

🔐 Security policy

Header signature

We sign the webhook events by including a signature in each event’s Leeway_Signature header.

Each webhook has a secret autogenerated and shared between us and the end user. Then, to sign the event, we compute an HMAC-SHA256 of the timestamp and the event’s body:

sha256 = HMAC_SHA256(timestamp + “.” + eventBody)

Finally, the Leeway_Signature header has the following format:

Leeway-Signature: t=1492774577, sha256=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

with t the timestamp and sha256 the previously computed hash.

Note that newlines have been added for clarity, but a real Leeway_Signature header is on a single line.

Signature check

When a client receive a webhook event, he should check that the signature is valid. Here is an example of how to do it:

import {createHmac, timingSageEqual} from 'crypto';  

function checkSignature(signature, secret, body) {
const timestamp = signature[0].split('=')[1];
const sig = Buffer.from(signature[1].split('=')[1], 'utf8');
const hmac = createHmac('sha256', secret);
const digest = Buffer.from(hmac.update(timestamp + '.' + JSON.stringify(body)).digest('hex'), 'utf8');
if (sig.length !== digest.length || !timingSafeEqual(digest, sig)) {
return false;
} else {
return true;
}
}

Note that it’s recommended to use a time constant-time string comparison to avoid timing attack vulnerability, that’s why we are using here timingSafeEqual instead of === (see https://en.wikipedia.org/wiki/Timing_attack)

Moreover, the client should check if the difference between the received timestamp et the current timestamp is within his tolerance (this is to avoid replay attack).

🔂 Retry policy

If the webhook respond with an other code than a 2xx or if the webhook take too much time (the timeout is set to 3s for the moment but the value may change), we consider the try as failed.

In this case, if the number of tries for this WebhookEvent is less than the max allowed (currently MAX_RETRIES = 3 ), we publish a new event in the queue with a delay of 5min (same here, value may change).

Otherwise, we don’t retry and disable the webhook.

Did this answer your question?