Webhooks
Verify signatures
Every request is signed with your endpoint secret. Check it before you trust the body.
Headers
X-Signature- Hex HMAC-SHA256 of
{X-Timestamp}.{raw body}, keyed with the endpoint secret. X-Timestamp- Unix seconds when this attempt was sent.
X-Event-Id- The envelope
id. X-Event-Type- The envelope
type.
Verify
- Refuse a timestamp more than 5 minutes from your clock. This also stops replays.
- Compute the HMAC over the raw bytes, before parsing the JSON.
- Compare in constant time.
const crypto = require("node:crypto")
const TOLERANCE_SECONDS = 300
function isAuthentic(secret, rawBody, signature, timestamp) {
if (!/^\d+$/.test(timestamp)) return false
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest("hex")
const a = Buffer.from(expected)
const b = Buffer.from(signature)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}