Webhooks
Best Practices
A checklist for a secure, reliable webhook receiver, covering verification, de-duplication, ordering, quick acknowledgement and secret handling.
Security
- Verify every signature against the raw body, in constant time, and reject timestamps more than 5 minutes from your clock. See Verifying Signatures.
- Keep the signing secret secret. Store it in a secret manager, never in source code. It is shown only once; if it is lost or exposed, rotate it.
- Use HTTPS with a valid certificate. Endpoints must be
https://on port 443 or 8443, on a public host name. - Treat the receiver URL as sensitive. Do not put credentials in it, and do not reuse it for anything else. Rely on the signature, not on the URL being secret.
- Do not rely on the source address. Deliveries are not sent from a fixed, published set of addresses, so an IP allowlist is not a substitute for verifying signatures.
- Treat the payload as a notification, not as authority. For anything consequential, fetch the current record through the API with a token you control before acting.
- Choose the owner deliberately. Payloads follow the owner's visibility, and the endpoint stops when the owner leaves or is no longer an Administrator or Executive. Give it an owner whose access matches what the receiving system should see.
Reliability
- Acknowledge quickly. Verify, store the event, return
2xx, then process it in the background. Deliveries time out after 10 seconds. - De-duplicate on the event
id. Delivery is at least once; the same event can arrive more than once, including after a redelivery. Record processed ids and skip repeats. - Do not assume order. Use
created_atto sort events, and refetch the record from the API when the latest state matters. - Ignore unknown event types and unknown fields, so new additions do not break your receiver.
- Handle thin events. If
data.objectonly containsidandobject, fetch the full record through the API. - Watch the delivery log. An endpoint whose deliveries keep failing is turned off after 72 hours, and resuming it does not replay what was missed.
Testing
- Use Send test event after creating an endpoint, and after every deployment of your receiver.
- Check that your receiver rejects a request with a changed body, a wrong signature and an old timestamp, not only that it accepts a genuine one. The test vector checks the signature computation itself.
- Make your receiver fail once on purpose, and confirm the next retry succeeds and is processed once.
Receiver checklist
| Check | Done when |
|---|---|
| Raw body available before JSON parsing | Signature verification uses the exact bytes received |
| Signature verified in constant time | A tampered body is rejected |
| Timestamp tolerance enforced | A delivery signed more than 5 minutes ago is rejected |
| Multiple signatures accepted | Rotating the secret causes no failed deliveries |
De-duplication on id | A repeated event is processed once |
| Fast acknowledgement | Responds in well under 10 seconds |
| Unknown types ignored | A new event type returns 2xx and is skipped |
| Thin events handled | An event with only id and object is fetched from the API |