Skip to main content
Courier signs every request. Verify the signature before you trust an event.

How it works

A webhook gets a whsec_... secret when you create it. Courier signs every request with HMAC-SHA256 in a courier-signature header:
Split the header on , and = to read t, a millisecond timestamp, and signature. Compute HMAC_SHA256(secret, "<t>.<raw_body>") and compare it to signature in constant time:
Sign against the raw request body, not a re-serialized object, so the bytes match what Courier hashed.

Delivery and retries

Return a 2xx 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

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.
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.
Almost always because the body was parsed and re-serialized before hashing. Hash the raw bytes of the request instead.