Skip to main content
Every delivery is signed with the webhook’s secret using the Standard Webhooks scheme. Verify before you act on a delivery: anyone can send a request to your endpoint.
  • Signed content: <webhook-id>.<webhook-timestamp>.<raw body>.
  • Key: the secret without its whsec_ prefix, base64-decoded.
  • Signature: base64 HMAC-SHA256, sent as v1,<signature>. Several space-separated signatures can be present; accept the delivery if one matches.
  • Reject timestamps more than 5 minutes from your clock to stop replays. The libraries below do this for you.
  • Verify the raw body bytes. Parsing and re-serializing the JSON changes the bytes and breaks the signature.
Any Standard Webhooks or Svix library verifies these deliveries unchanged (the svix-* headers carry the same values). To check a verifier before your first delivery, use the test vector.

Node.js with standardwebhooks

Node.js

Python with standardwebhooks

Python

By hand: Node.js

No dependencies; headers are the request headers with lower-case names and body the raw body as a Buffer. now (Unix seconds) is only for checking the test vector.
verify.mjs

By hand: Python

Standard library only; body is the raw request body as bytes. Every failure raises ValueError. now (Unix seconds) is only for checking the test vector.
verify.py

Test vector

Check a verifier offline, before any delivery arrives, with this fixed delivery. Its signature was made by the code that signs real deliveries.
Body
The vector was signed at a fixed instant in the past, 2026-01-01T00:00:00.000Z, so a verifier that checks the timestamp against a real clock rejects it as too old. It passes only with that check disabled or the clock pinned to 1767225600. The hand-written verifiers above take the clock as their last argument:
Node.js, by hand
Python, by hand
The standardwebhooks libraries have no clock option. Compute the signature with their sign and compare it with the expected value instead:
Node.js, standardwebhooks
Python, standardwebhooks
Changing any byte of the body, the id or the timestamp must make your verifier reject the vector.

When verification fails

  • The body was parsed first. Frameworks that parse JSON before your handler change the bytes. Read the raw body (express.raw, request.get_data()).
  • The wrong secret. Each webhook has its own. After a rotation, use the new secret; the old one also verifies for 24 hours.
  • The prefix. Strip whsec_ and base64-decode the rest to get the key; do not use the string itself as the key.
  • Clock skew. A timestamp more than 5 minutes off is rejected. Keep your server’s clock synchronized.
Send a test event from the webhook’s settings and compare with its entry under Deliveries.