Signing & verification
Signing & verification
Verify every webhook with the v1= HMAC scheme before you act on it.
The signature is v1= + hex HMAC-SHA256, keyed by your endpoint secret, over the bytes
"{timestamp}.{delivery_id}." + raw_request_body. Verify with a constant time compare
and reject anything older than 300 seconds. During a secret rotation grace window the
header carries more than one comma-separated v1= candidate — accept the delivery if
any candidate matches your secret.
Always verify against the raw request bytes, before any JSON parsing.
With the SDKs (recommended)
The SDK helpers implement the scheme (multi-candidate + tolerance) for you.
Raw recipe (no SDK)
This recipe is exercised verbatim by
tests/test_webhooks.py::test_guide_raw_verification_recipe:
Respond 2xx once you have durably accepted the event. Any non-2xx (or a timeout) is a
failed attempt and will be retried.
Verify against the raw bytes before parsing JSON, and use a constant-time compare
(hmac.compare_digest) — never ==. A webhook is a notification: re-read authoritative
state from the REST API before moving money.

