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

  1. POST /v1/events stores the event and returns HTTP 202. Your request is finished.
  2. Flowyn signs the payload with the endpoint secret and POSTs it. The response is attempt 1.
  3. A failure records the attempt and schedules the next one.
  4. Up to 8 attempts are made over roughly a day and a half.
  5. A 2xx marks the delivery delivered. Failed deliveries stay readable and can be replayed.
AttemptWait before (approx.)Cumulative (approx.)
1immediate0
25 s5 s
35 min5 min
430 min35 min
52 h2 h 35 min
65 h7 h 35 min
710 h17 h 35 min
810 h27 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:

bash
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.

Next step

Try a delivery with a failing first attempt in the Playground.