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:
- Split the header into
tandv1. - Compute
HMAC-SHA256over the timestamp, a literal., and the raw request body, not a re-serialized version of the parsed JSON. - Compare against
v1with a constant-time comparison. - 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.