Verifying signatures
Every delivery carries an X-AwardSpring-Signature header. It proves the body came from AwardSpring and was not altered in transit.
The scheme
-
Take the
tvalue from the header. -
Build the signed payload: the timestamp, a literal
., then the raw request body. -
Compute
HMAC-SHA256over that string using your endpoint’swhsec_...secret as the key. -
Hex-encode the result in lowercase and compare it against any
v1value in the header, using a constant-time comparison.
Sign the raw bytes you received. Parsing the JSON and re-serializing it changes whitespace and key order, and the signature will never match. Capture the body as a string before any framework deserializes it.
Example
A naive dict() of the header parts keeps only the last v1. During a rotation grace window that silently drops the other valid signature, so parse the v1 values as a list, as above.
Check the timestamp
The signature alone does not stop a replay: someone who captured a valid delivery could send those exact bytes and headers again, forever. Rejecting deliveries whose t is far from your own clock closes that window.
Choose the tolerance deliberately. Too tight and a legitimate retry — AwardSpring waits up to six hours between attempts — arrives outside it. The retry is signed fresh at each attempt, with a current t, so a few minutes of tolerance is enough for real deliveries; it is the captured copy that gets old.
Rotating a secret
Rotate from Settings → API → Webhooks, in one of two modes:
With the grace window, deliveries carry two signatures:
Verifying against any v1 — as the examples above do — means your integration keeps working across the whole window, and you can deploy the new secret whenever you like inside it. An implementation that reads only the first or last v1 will start failing half its deliveries the moment a rotation begins.
Use immediate rotation only when the secret is believed to be compromised, and expect deliveries to fail until the new one is deployed. Those failures retry, so a fast redeploy usually loses nothing.