How it works
A webhook gets awhsec_... secret when you create it. Courier signs every request with HMAC-SHA256 in a courier-signature header:
, and = to read t, a millisecond timestamp, and signature. Compute HMAC_SHA256(secret, "<t>.<raw_body>") and compare it to signature in constant time:
Delivery and retries
Return a2xx fast and do slow work asynchronously. Courier treats a slow or failing endpoint as a failed delivery. A delivery times out after 10 seconds.
A failed delivery retries with a backoff. It starts at a few seconds, grows to 15-minute intervals, and runs for roughly a day before the event is dropped. Most 4xx responses are non-retryable and dropped immediately.
Limits & behavior
- Verify against the raw body. Re-serializing the JSON changes the bytes, so the signature will not match.
- A destination is never auto-disabled. Courier keeps retrying a permanently broken endpoint until each event ages out.
- Events that age out are gone. After roughly a day of failures the event is dropped. Backfill from the if you need it.
FAQ
How do I know a request really came from Courier?
How do I know a request really came from Courier?
Verify the
courier-signature header. Compute HMAC_SHA256(secret, "<t>.<raw_body>") with your webhook’s whsec_ secret and compare it to the header’s signature. Reject anything that does not match.What happens if my endpoint is down?
What happens if my endpoint is down?
Courier retries with an increasing backoff for about a day, then drops the event. The destination is not disabled. Fix the endpoint and later events deliver normally, but events that aged out during the outage are gone.
Why does my signature check keep failing?
Why does my signature check keep failing?
Almost always because the body was parsed and re-serialized before hashing. Hash the raw bytes of the request instead.