How to implement reliable webhooks and webhook retries
What makes a webhook unreliable, how retries with backoff work, why receivers must deduplicate, and how to implement reliable webhook delivery with Flowyn events.
Why a plain webhook is unreliable
A webhook is one HTTP POST from your system to somebody else's. It fails when the receiver is down, slow, rate-limited, or returns 500. If the POST runs inside your request handler, the failure is lost with the request, and nothing records that it happened.
What reliable webhook delivery requires
- Store first. Persist the event before trying to deliver it, so nothing is lost if delivery fails.
- Sign every delivery. Receivers must be able to verify that the payload came from you.
- Retry with backoff. Wait longer after each failure instead of retrying in a tight loop.
- Record every attempt. Status code, latency, and timestamp, appended and never overwritten.
- Replay on demand. Resend a past event after a receiver bug is fixed.
- Group repeated failures. One issue for “destination returns 500”, not hundreds of alerts.
How webhook retries work in Flowyn
POST /v1/eventsstores the event and returns HTTP 202. Your request is finished.- Flowyn signs the payload with the endpoint secret and POSTs it. The response is attempt 1.
- A failure records the attempt and schedules the next one.
- Up to 8 attempts are made over roughly a day and a half.
- A 2xx marks the delivery
delivered. Failed deliveries stay readable and can be replayed.
| Attempt | Wait before (approx.) | Cumulative (approx.) |
|---|---|---|
| 1 | immediate | 0 |
| 2 | 5 s | 5 s |
| 3 | 5 min | 5 min |
| 4 | 30 min | 35 min |
| 5 | 2 h | 2 h 35 min |
| 6 | 5 h | 7 h 35 min |
| 7 | 10 h | 17 h 35 min |
| 8 | 10 h | 27 h 35 min |
Why receivers must deduplicate
Delivery is at-least-once. A receiver can process a request and then fail to return a response; Flowyn sees a failure and retries, and the receiver sees the event twice. Store the webhook-id header, which stays the same across retries, and ignore repeats.
Implement it
Create the event with an idempotency key so a retried create cannot duplicate it.
curl -X POST https://api.flowyn.dev/v1/events \
-H "Authorization: Bearer $FLOWYN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-123-completed" \
-d '{
"type": "order.completed",
"payload": {
"order_id": "ord_123"
}
}'Read the attempts later, or replay after a fix:
curl https://api.flowyn.dev/v1/events/0f8e4a52-3c1d-4e7b-9a26-5d1b7c3e9f10/attempts \
-H "Authorization: Bearer $FLOWYN_API_KEY"
curl -X POST https://api.flowyn.dev/v1/events/0f8e4a52-3c1d-4e7b-9a26-5d1b7c3e9f10/replay \
-H "Authorization: Bearer $FLOWYN_API_KEY"Webhook retry best practices
- Return 2xx quickly, then process asynchronously in the receiver.
- Verify the Standard Webhooks signature and reject old timestamps.
- Keep attempt history visible to the people who debug integrations.
- Watch the receiver with an HTTP monitor so an outage is an incident, not a surprise.
Related
Next step
Try a delivery with a failing first attempt in the Playground.
