Skip to main content
Every webhook delivery is signed with HMAC-SHA256, and verifying that signature is how you know a delivery came from us. Build the check described here before you act on anything.
Every account has a signing secret. Reveal it once in Settings β†’ Integration β†’ Webhook sender check (or on the Webhooks tab) and store it where your server reads it. It cannot be shown a second time; if you lose it, or think someone else has it, rotate it there β€” the old secret stops working immediately.

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.
The dashboard’s Send test payload button signs exactly like a real delivery β€” same header, same envelope, same signed content. Its event is payment.test and its data.test is true, so route it away from fulfilment. If your handler verifies the test, it verifies production.

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

Keep it out of your source tree. If it’s ever exposed, rotate it in the dashboard β€” the old one stops working immediately.
We never send a delivery unsigned. If a signing secret were ever missing on our side, the delivery is held and shows as failed in your delivery log instead of arriving without a signature. So a request to your endpoint that does not carry a valid signature did not come from us β€” drop it.