Skip to content

Verify signatures

Every request includes an X-Hooks-Signature header. Verify it against the raw body using your endpoint’s signing secret before acting on the event.

The header contains a timestamp and one or more signatures: t=1739980800,v1=8f2c…. Compute HMAC-SHA256(secret, timestamp + "." + rawBody) and compare it in constant time.

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

When the endpoint uses a known provider, Hooks verifies the provider’s own signature instead of the generic scheme:

Provider Header Scheme
github X-Hub-Signature-256 sha256=<hex HMAC of raw body>.
stripe Stripe-Signature t=<ts>,v1=<hex HMAC of "<ts>.<body>">, 5-minute tolerance, multiple v1 accepted.
slack X-Slack-Signature v0=<hex HMAC of "v0:<ts>:<body>">, 5-minute tolerance.
linear Linear-Signature Hex HMAC of the raw body plus a body webhookTimestamp within 60 seconds.

Provider signatures are checked against the endpoint secret you configured in the console.

Never compare signatures with ==. Use a constant-time function and reject requests whose timestamp is more than five minutes old to limit replay attacks.