Node.js · 10/19 6 min
Webhooks and signatures
HMAC over the raw body, timing-safe compare, replay windows, and answering 2xx fast.
A webhook is someone else's server calling yours. You must prove the call is genuine, process it once, and answer quickly.
Verify the signature
const crypto = require("node:crypto");
function verify(rawBody, header, secret) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(header ?? "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
- Sign the raw bytes.
JSON.stringify(req.body)afterexpress.json()may differ in spacing or key order, so the HMAC won't match. Useexpress.raw({ type: "application/json" })on the webhook route. timingSafeEqualthrows if the lengths differ: check length first.===on secrets leaks timing; use the safe compare.- Bad signature → 401, and
returnso the handler stops.
Stop replays
Providers send a timestamp, usually in seconds, and often sign timestamp + "." + body.
const age = Math.floor(Date.now() / 1000) - Number(ts);
if (!Number.isFinite(age) || Math.abs(age) > 300) return res.status(400).end();
Process once
Providers retry until they get a 2xx, so the same event can arrive several times. Store the event id; if you've seen it, answer 200 and do nothing.
Answer fast, and with 2xx
- Unknown event types: 200 and ignore. A 4xx makes the provider retry forever.
- Do slow work after responding, or queue it.
- A
switchonevent.typeneeds abreak/returnper case; fall-through runs the next handler too.
Practise it now
Next in Node.js
Caching without stale bugs