HMAC Webhooks
Inbound verification for the timestamped-HMAC webhook scheme — a single header carrying a Unix timestamp and one or more HMAC signatures:
<sig-header>: t=<unix-seconds>,v1=<hmac-sha256-hex>[,v1=<rotated>]
The signed payload is <timestamp>.<raw-body>, keyed on the endpoint signing secret. This is the scheme Stripe and Tailscale (among others) use. It is DISTINCT from the StandardWebhooks scheme that b.standardWebhooks verifies, which uses three separate headers and an <id>.<ts>.<body> payload.
Verification refuses a timestamp outside the tolerance window (replay defense), checks EVERY signature value under the version field (so a rotated secret verifies with no downtime), compares with b.crypto.timingSafeEqual, and ignores signature versions it does not understand. Always verify against the EXACT received bytes — never a re-serialized JSON body.
b.webhookHmac.verify(opts) #
{
header: string, // the raw signature header value ("t=...,v1=...")
rawBody: Buffer | string, // the exact received body bytes
secret: Buffer | string, // the endpoint signing secret
profile: string, // "stripe" | "tailscale" — sets tsField/sigField/alg
tsField: string, // default: "t"
sigField: string, // default: "v1"
alg: string, // default: "hmac-sha256" (also "hmac-sha512")
toleranceSec: number, // default: 300 (5 minutes)
}
Verify an inbound webhook signed with the timestamped-HMAC scheme (t=<ts>,v1=<hmac>). Refuses on a missing/garbled header, a timestamp outside the tolerance window (replay), or an HMAC mismatch; returns { valid: true, timestamp } when a signature matches.
The signed payload is <timestamp>.<raw-body> — pass the EXACT bytes received, not a parsed-then-re-serialized JSON body, or the HMAC will not reproduce. Every value under the signature field is checked, so a rotated secret (two v1= values) verifies without downtime. Comparison is constant-time; unrecognized signature versions are ignored.
var v = b.webhookHmac.verify({
header: req.headers["stripe-signature"],
rawBody: rawBody,
secret: process.env.WHSEC,
});
// → { valid: true, timestamp: 1614556828 }
Last updated 2026-08-08T16:39:49.652Z by seeder.