A webhook endpoint on the public internet will receive requests that did not come from your provider. Signature verification is the line between processing a payment event and letting anyone invent one.
Short answer: compute an HMAC over the exact raw request bytes using the provider's signing secret, compare it with the supplied signature in constant time, reject stale timestamps, then parse and process the event idempotently.
const expected = createHmac("sha256", secret).update(rawBody).digest();
if (!timingSafeEqual(expected, receivedSignature)) throw new Error("invalid signature");
| Check | Stops |
|---|---|
| Raw-body HMAC | Modified or forged payloads |
| Constant-time compare | Signature timing leaks |
| Timestamp tolerance | Recorded request replay |
| Event ID storage | Provider retries causing duplicate work |
Parse JSON after verification. Re-serializing parsed JSON can change whitespace or key ordering and makes the bytes differ from what the provider signed.
Return a fast success after persisting the event or queueing it; process slow work asynchronously. A webhook endpoint must be both authentic and retry-safe—HMAC handles only the first half.
Cover photo by Laura Gigch on Pexels.
