What is a webhook delivery?
A webhook delivery is one HTTP attempt by the source system to deliver an event to your endpoint, including retries. One event can produce many deliveries — if your endpoint times out, the source retries, generating a second delivery with the same event ID but a new attempt counter. The lifecycle has four states: pending, in-flight, delivered (2xx), or failed (timeout, non-2xx, TLS error, DNS failure). A 2xx response is the only success signal the source has — never run business logic between persistence and the response, or you'll silently lose events.
An event is the thing that happened; a delivery is one attempt to tell you about it. If your endpoint times out and the source retries, that is a second delivery of the same event. Five retries means five deliveries, all with the same event ID but different delivery attempt numbers.
Most providers expose this distinction in their dashboards: Stripe shows you "Webhook attempts" per event, GitHub shows the "Recent Deliveries" tab per webhook, Shopify shows delivery history. Each delivery has its own timestamp, response status, and response body — which is what you use when debugging "why didn't my handler run?"
The delivery lifecycle has four states worth distinguishing. Pending: source queued the event, hasn't dialed your endpoint yet. In-flight: the request is open. Delivered: your endpoint returned 2xx. Failed: timeout, non-2xx response, TLS error, or DNS failure. Most providers retry every failed delivery on a schedule — Stripe retries up to 3 days with exponential backoff, GitHub retries 8 times across ~24 hours.
A subtle gotcha: a 2xx response is the only signal of success the source has. If your handler returns 200 but throws an exception after writing to the response, the source thinks the delivery succeeded — your event is silently lost. The correct shape is: validate, persist to a queue or DB, then return 200. Never run business logic between the persistence step and the response.
Deliveries carry headers your handler should care about beyond just the signature. The event ID header (X-GitHub-Delivery, Stripe-Signature includes the t= timestamp). The User-Agent identifies the source (Stripe/1.0, GitHub-Hookshot/...). The delivery attempt counter — present in some providers — tells you "this is retry #3."
Log every delivery, indexed by event ID. When a customer says "I never got my receipt," the only way to triangulate is to look up the Stripe event in their dashboard, find the delivery attempts, find the matching log line on your side, and trace what your handler did. Without that index, you are guessing.
See Webhook Delivery in real traffic
WebhookWhisper captures every webhook with full headers, body, signature, and timing — so concepts like webhook delivery stop being abstract and become something you can inspect.
Start Free