Skip to main content
Anyone who learns your webhook URL can send a request to it. Without a check, they could post “invoice paid” and your platform would believe it. Saasybill signs every request so you can tell a real one from a forgery.
Always verify the signature before you act on a webhook. Reject any request that fails.

The signature header

Every request has a Saasybill-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.
Three details matter.
  • 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.