Skip to content

Verify requests

Every delivery carries a SendSignal-Signature header:

SendSignal-Signature: t=1724059000,v1=5257a869e7...

v1 is HMAC-SHA256(signing_secret, "<t>." + rawBody) in hex, where the signing secret is the whsec_… value returned when you created the webhook. To verify:

  1. Split the header into t and v1.
  2. Compute HMAC-SHA256 over the timestamp, a literal ., and the raw request body, not a re-serialized version of the parsed JSON.
  3. Compare against v1 with a constant-time comparison.
  4. Optionally reject stale timestamps to prevent replay.

In Node:

import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret: string, header: string, rawBody: string): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Reject anything that fails verification with a 4xx, but never reflect the expected signature in the response.