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));}Provider-specific schemes
Section titled “Provider-specific schemes”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.