Skip to main content
Every webhook is signed with HMAC-SHA256 using your webhook secret. Verify before you act on anything.

The scheme

The signed content is the timestamp and the raw body, joined by a dot:
Sign the raw body bytes. If your framework parses JSON and you re-serialise it, key order and whitespace change and the signature will never match. Configure a raw-body parser for this route.

Implementation

Checklist

Constant-time compare

timingSafeEqual, hmac.compare_digest, hash_equals, hmac.Equal. Never == on the signature.

Check the timestamp

Reject anything older than about five minutes, or a captured delivery can be replayed indefinitely.

Raw body only

Configure your framework to hand you unparsed bytes on this route.

Secret in env, not source

Rotate it in the dashboard if it’s ever exposed.
If your webhook secret is unset, the signature is delivered as the literal string none. Treat that as a failure and refuse the delivery — set a secret before going live.