The signature header
Every request has aSaasybill-Signature header.
How to verify
1
Split the header
Read
t and every v1 from the header.2
Check the timestamp
Reject the request if
t is more than five minutes from your clock. This stops someone replaying an old, genuine request.3
Compute the expected signature
Build the string
<t>.<raw body>: the timestamp, a full stop, then the request body exactly as received. Compute its HMAC-SHA256 with your signing secret as the key, and encode it as hex.4
Compare
Compare your result with each
v1 in constant time. If any matches, the request is genuine.- Use the raw body. A body that was parsed and serialised again isn’t the bytes Saasybill signed. Read the body as bytes or as a string before your framework parses it.
- Use the whole secret. The key is the complete signing secret, including the
whsec_prefix. - Compare in constant time. Use your language’s constant-time comparison, not
==.
Code
Test your check
1
Send a test event
Open the endpoint’s menu in Developer Settings, then Webhooks, and click Send Test Event.
2
Confirm it passes
Your handler should verify the signature and return
200. The delivery shows as Delivered under Recent Deliveries.3
Confirm a forgery fails
Send a request to your endpoint yourself, with a wrong
Saasybill-Signature. It should return 400.Rolling your secret
Roll the secret if it might have leaked, or on a schedule.1
Roll it
Open the endpoint’s menu and click Roll Secret. Copy the new secret from the Your New Signing Secret dialog.
2
Deploy the new secret
The old secret keeps signing for 24 hours. During that time each request carries two
v1 values, one per secret. The code above accepts either, so you can deploy without missing an event.3
Done
After 24 hours, only the new secret signs.
A request that fails verification with the right secret usually means the body was changed before you hashed it. Check that no middleware parsed or re-encoded it.