Skip to main content

Verifying Webhook Requests

Every single Tribe webhook request includes X-Tribe-Signature and X-Tribe-Request-Timestamp headers. We highly recommend verifying all webhook requests using the X-Tribe-Signature and the Signing secret provided under "Credentials" section of your app in the Tribe developers portal.

note

Not verifying X-Tribe-Signature will let third parties misuse your webhook endpoint by faking POST requests and can be dangerous.

Webhook Verification Steps#

Here's an overview of the process to validate a signed request from Tribe:

  1. Retrieve the X-Tribe-Request-Timestamp header on the HTTP request and the raw body of the request.
  2. Concatenate the extracted timestamp, and the raw body of the request to form a basestring. Use a colon (:) as the delimiter between the two elements.
  3. With the help of HMAC SHA256 implemented in your favorite programmig language, hash the above basestring, using the Tribe Signing secret as the key.
  4. Compare this computed signature to the X-Tribe-Signature header on the request.
note

Make sure that the request body contains no headers and is not deserialized in any way. Use only the raw request payload.

Preventing replays#

Tribe does not ensure that an event will be triggered just once. As an example, Tribe will retry an event if it times out.

Every single Tribe webhook request includes X-Tribe-Request-Timestamp and a unique data.id.

To ensure that an event is handled only once on your side, we recommend checking data.id and making sure you haven't processed an event with that unique ID before. We also recommend checking X-Tribe-Request-Timestamp and ignore any events older than 15 minutes before the request timestamp.

Doing so will also prevent replay attacks on your endpoints in case it happens.

Webhook Verification Example#

Here is how you can verify Tribe webhook requests using Node.js.

First we need to have a function that generates the signature based on the timestamp, raw body, and the signing secret. We can easily do so using the following function:

import * as crypto from 'crypto';
export const getSignature = ({ secret, body, timestamp }) => {
return crypto.createHmac('sha256', secret).update(`${timestamp}:${body}`).digest('hex');
};

Next, we need to create a function that compares the generated signature with X-Tribe-Signautre and also makes sure that the request is not older than 5 minutes based on the X-Tribe-Request-Timestamp to prevent replay attacks.

const MILLISECONDS_IN_MINUTE = 1000 * 60;
export const verifySignature = ({ signature, secret, body, timestamp }) => {
const timeDifference = (timestamp - new Date().getTime()) / MILLISECONDS_IN_MINUTE;
if (timeDifference > 5) return false;
const hash = getSignature({ secret, body, timestamp });
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash));
};

This function will return true if all is good.

If we're using Express, we can create a middleware that throws an error if the signature is not verified. To do so, we need to use body-parser to pass the raw body to the request:

import bodyParser from 'body-parser';
app.use(
bodyParser.json({
verify: (req, res, buf) => {
req['rawBody'] = buf;
},
}),
);

Last but not least, we can introduce a new middleware to our app to verify the signature:

app.use((req, res, next) => {
// Filled by body-parser
const rawBody = req['rawBody'];
const timestamp = parseInt(req.header('x-tribe-request-timestamp'), 10);
const signature = req.header('x-tribe-signature');
try {
if (rawBody && verifySignature({
body: rawBody,
timestamp,
signature,
secret: SIGNING_SECRET // Your signing secret
})) {
return next();
}
} catch (err) {
console.error(err);
}
return next(new Error(403, 'The x-tribe-signature is not valid.'));
});
note

You can check our Tribe Starter App on GitHub to see signature verification in action.