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 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.