Developers · Webhooks
Verify the signature
Every event is signed with the webhook's secret. Verify it before trusting its content.
The headers
Events follow Standard Webhooks: any library for that specification verifies them with the secret you get when you create the webhook.
webhook-id: the event id, the same on every retry. Use it to avoid processing an event twice.webhook-timestamp: when it was sent, in seconds. Discard events more than 5 minutes old.webhook-signature:v1,followed by the base64 HMAC-SHA256 ofid.timestamp.body.
Verify in Node, with no libraries
const crypto = require('crypto');
const signed = `${headers['webhook-id']}.${headers['webhook-timestamp']}.${rawBody}`;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto.createHmac('sha256', key).update(signed).digest('base64');
const valid = headers['webhook-signature']
.split(' ')
.some((part) => part === `v1,${expected}`);
Use the body exactly as it arrived (rawBody), before parsing it as JSON: any whitespace change breaks the signature.
Verify in Python
import base64, hashlib, hmac
signed = f"{h['webhook-id']}.{h['webhook-timestamp']}.".encode() + raw_body
key = base64.b64decode(secret.removeprefix("whsec_"))
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
valid = any(part == f"v1,{expected}" for part in h["webhook-signature"].split())
Rotate the secret
You can rotate the secret from Webhooks. The previous one keeps signing for 24 hours and the header carries both signatures, so your system keeps working while you update it.
Reviewed on