Skip to main content
A Webhook Delivery tracks the delivery attempts for an event to a webhook endpoint. When an event is dispatched, we create a delivery for each matching webhook subscription. Each delivery has its own unique ID (sent in the Webhook-Id header) and progresses through the following statuses:
StatusDescription
pendingThe delivery has been created and is waiting to be dispatched.
in_progressThe delivery is actively being attempted.
succeededYour endpoint returned a 2xx response.
failedAll retry attempts have been exhausted without a successful response.

Retry schedule

We use at-least-once delivery, which means we guarantee that every event reaches your endpoint at least once — but it may arrive more than once in some cases (see Handling duplicates). If your endpoint doesn’t return a 2xx response, we’ll retry the delivery with exponential backoff and jitter, up to 10 attempts:
AttemptAverage delayMaximum delay
1Immediate
2~0.5s1s
3~3s6s
4~18s36s
5~1m 48s3m 36s
6~10m 48s21m 36s
7~1h 4m2h 9m
8~6h 28m12h 57m
9~12h24h
10~12h24h
The total delivery window spans approximately 43 hours on average and up to 3.6 days in the worst case. After 10 failed attempts, the delivery is marked as failed and no further retries are made.

Disabling a webhook

If a webhook is disabled while a delivery is in progress, any future retry attempts will be cancelled and the delivery will be marked as failed. However, if a delivery attempt is already in flight (i.e. we’re actively waiting for your endpoint to respond), that attempt will complete normally before the cancellation takes effect.

Delivery attempt errors

Each failed delivery attempt records the type of error encountered:
Error typeDescription
httpYour endpoint returned a non-2xx HTTP status code.
timeoutThe connection or read timed out.
dnsDNS resolution for your endpoint failed.
tlsTLS certificate verification failed.
connectionThe connection was refused, reset, or a network error occurred.
validationThe webhook URL failed validation (e.g. it resolves to a private IP address).
webhook_disabledThe webhook was disabled while this delivery was still in progress.
unknownAn unhandled exception occurred during delivery.

Handling duplicates

Because delivery is at-least-once, your webhook endpoints may occasionally receive the same event more than once. We recommend making your event processing idempotent to handle this gracefully. A straightforward approach is to use the Webhook-Id header to deduplicate:
  1. When you receive a delivery, check whether you’ve already processed a delivery with that Webhook-Id.
  2. If you have, return 200 immediately without reprocessing.
  3. If you haven’t, process the event and record the ID.
def handle_webhook(request):
    delivery_id = request.headers["Webhook-Id"]

    if already_processed(delivery_id):
        return 200

    process_event(request.json())
    mark_processed(delivery_id)
    return 200

Ordering

The order of your received webhook events may not match the order they were created. Network latency and retries can cause events to arrive out of sequence. If you need to determine the correct order, use the created_at timestamp on each event rather than relying on delivery order.