Verifying webhooks
Check every webhook's signature before you act on it, so nobody can fake or replay an event.
Each request has an x-tracking-signature header with a Unix timestamp and an HMAC-SHA256 digest:
x-tracking-signature: t=1792560000,v1=<hex digest>
digest = HMAC_SHA256(webhook_secret, "<t>.<raw request body>")Your webhook secret is shown once, when you create the endpoint.
import { createHmac, timingSafeEqual } from "node:crypto";
// header: the x-tracking-signature value, e.g. "t=1792560000,v1=5f2c…"
// rawBody: the request body exactly as received, before JSON.parse
export function verifyTrackifiedSignature(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
const timestamp = Number(parts.t);
if (!timestamp || !parts.v1) return false;
// Reject events older than five minutes to stop replays.
if (Math.abs(Date.now() - timestamp * 1000) > 5 * 60 * 1000) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
const actual = Buffer.from(parts.v1, "hex");
return actual.length === expected.length && timingSafeEqual(actual, expected);
}Use the raw body
Compute the digest over the body exactly as it arrived. Parsing and re-serialising the JSON changes the bytes, and the signature stops matching.
Updated