How Webhook Retries Work

What happens when your endpoint does not answer with a 2xx

When an event such as order.paid fires, we deliver it to your endpoint with a POST. If that attempt fails, we keep trying on a growing schedule for about 36 hours before giving up.

Webhook delivery and retry path
Webhook delivery and retry path11 nodes. Retry loop contains Delivery queue, Worker POSTs to your endpoint, 2xx within 10 s?, 410 Gone?, Attempts left?, Wait with backoff Event created → Delivery queue (enqueue); Delivery queue → Worker POSTs to your endpoint (next attempt); Worker POSTs to your endpoint → 2xx within 10 s?; 2xx within 10 s? → Delivered (yes); 2xx within 10 s? → 410 Gone? (no); 410 Gone? → Endpoint disabled (yes); 410 Gone? → Attempts left? (no); Attempts left? → Wait with backoff (yes); Attempts left? → Dead-letter queue (no); Wait with backoff → Delivery queue (requeue); Dead-letter queue → Manual replay (on request)q → send: next attemptsend → okok → perm: noperm → left: noleft → wait: yeswait → q: requeueevt → q: enqueueok → done: yesperm → off: yesleft → dlq: nodlq → replay: on requestQUEUEDelivery queueFNWorker POSTs to your endpoint10 s timeout2xx within 10 s?410 Gone?Attempts left?FNWait with backoff30 s up to 24 hSVCEvent createdfor example order.paidSVCDeliveredEXTEndpoint disabledowner emailedQUEUEDead-letter queuekept 14 daysTEAMManual replaydashboard or APIRETRY LOOPNEXT ATTEMPTNONOYESREQUEUEENQUEUEYESYESNOON REQUEST
  1. Retry loop: Delivery queue, Worker POSTs to your endpoint, 2xx within 10 s?, 410 Gone?, Attempts left?, Wait with backoff
  2. Event created → Delivery queue: enqueue
  3. Delivery queue → Worker POSTs to your endpoint: next attempt
  4. Worker POSTs to your endpoint → 2xx within 10 s?
  5. 2xx within 10 s? → Delivered: yes
  6. 2xx within 10 s? → 410 Gone?: no
  7. 410 Gone? → Endpoint disabled: yes
  8. 410 Gone? → Attempts left?: no
  9. Attempts left? → Wait with backoff: yes
  10. Attempts left? → Dead-letter queue: no
  11. Wait with backoff → Delivery queue: requeue
  12. Dead-letter queue → Manual replay: on request
SVCQUEUEFNDECISIONEXTTEAMfocusboundaryrequest / flowasync / reply / optional

In short

Delivery is at least once. Any 2xx response within 10 seconds counts as success. A timeout, a connection error, a 5xx and every 4xx other than 410 all count as a failure and enter the retry loop.

The retry schedule

Wait before each retry
Retry 10.5 min
Retry 22 min
Retry 310 min
Retry 460 min
Retry 5180 min
Retry 6480 min
Retry 71,440 min
Data
retrywait
Retry 10.5 min
Retry 22 min
Retry 310 min
Retry 460 min
Retry 5180 min
Retry 6480 min
Retry 71,440 min
retrywait before ittotal since first tryjitter window
130 s30 s24 s to 36 s
22 min2.5 min96 s to 144 s
310 min12.5 min8 to 12 min
41 habout 1 h 12 min48 to 72 min
53 habout 4 h 12 min2.4 to 3.6 h
68 habout 12 h 12 min6.4 to 9.6 h
724 habout 36 h 12 min19.2 to 28.8 h

What your endpoint should do

Because delivery is at least once, the same event can arrive more than once, for example when your server processed it but its 200 was lost. Every retry carries the same event id, so deduplicate on it. Respond fast and do the slow work afterward.

POST /hooks/payments HTTP/1.1
Webhook-Id: evt_01JA7Q3M
Webhook-Attempt: 3
Webhook-Timestamp: 1760803200
Content-Type: application/json
@app.post("/hooks/payments")
def receive():
    event = request.get_json()
    if already_processed(event["id"]):   # retries reuse the same id
        return "", 200
    queue.put(event)                     # slow work happens later
    return "", 200

Back to the examples