Routing Failed Webhooks to an Error Endpoint
When a provider sends a webhook, it delivers an event to an HTTP endpoint operated by the receiving service. If the endpoint…
A webhook provider sends an event to an integrator’s endpoint, usually with an HTTP request to a URL set up to receive events. A delivery state tells the integrator where the provider is in sending that event. Clear state names show whether the provider has not sent the event yet, received a success response, hit an error, or stopped trying.
The labels queued, delivered, failed, retried, and terminal failure give integrators a shared vocabulary, but providers do not use one standard set of state definitions or retry rules. Documentation should explain each label by saying what the provider did and what the integrator should do next. In particular, “delivered” should name the endpoint’s response. It should not suggest that the integrator has finished later business processing.
A provider should say whether a state describes the event overall or one delivery attempt. An attempt is one request to the integrator’s endpoint. The table gives recommended meanings and the next action each state suggests.
| State | What the provider should mean | What the integrator should do |
|---|---|---|
| Queued | The provider recorded the event for delivery, but has not yet made the relevant attempt. The provider should clarify whether this includes an attempt scheduled for later. | Wait and monitor. If the event stays queued beyond the provider’s stated delivery window, check its status page, delivery logs, or support channel. |
| Delivered | The endpoint returned the provider’s documented success response. Many providers count an HTTP 2xx response as success; 2xx is the range of HTTP status codes that indicates a successful request. | Check the integrator’s own logs to confirm receipt and any later processing. Use the event ID to correlate records and spot duplicate attempts. |
| Failed | A particular attempt did not meet the provider’s success criteria. This state describes that attempt; the provider should separately show whether another attempt is planned. | Check the response code or transport error, then investigate the endpoint, network, or response time. Confirm whether the provider will retry automatically or requires a redelivery request. |
| Retried | The provider either scheduled a later attempt or made one after an earlier attempt failed. It should say which meaning applies and show the attempt number and timing. | Inspect the latest attempt and its failure reason. If the previous request timed out, check whether it reached the endpoint before repeating work, since the endpoint might have received a request whose response did not reach the provider. |
| Terminal failure | The provider has stopped automatic attempts for the event, or otherwise ended delivery. It should state the condition that ends attempts and whether the event remains recoverable. | Fix the underlying issue, then use the provider’s redelivery or replay option. If replay is unavailable or the event is no longer accessible, reconcile missing data through another provider-supported route. |
“Queued” needs particular care: it can mean an event awaits its first attempt or that a retry is waiting. A queue on the receiver’s side serves a different purpose. GitHub recommends that webhook receivers put payloads in a queue for later processing so they can respond promptly. That recommendation does not define a provider’s queued delivery state. Providers should clarify whether they have accepted the event for sending and whether sending has started. They should also say whether queued includes scheduled retries. GitHub’s webhook best practices describe the receiver-side queueing recommendation.
A “delivered” state usually records the response that the integrator’s endpoint sent to the provider. For example, Svix’s receiving documentation describes a 2xx response as the signal that a webhook was processed. GitHub and Shopify also define successful delivery by qualifying 2xx responses. The provider can report that response, but it cannot tell from it whether the integrator’s later asynchronous work succeeded.
Integrators should acknowledge a webhook only after they have safely accepted the event for handling. They can then check their logs or data store to confirm later work. The provider’s delivery record and the integrator’s processing record answer different questions: did the endpoint accept the request, and did the integrator complete the work it triggered?
A provider should document its exact success criteria and response deadline. Those rules differ: Shopify documents a five-second response limit, while GitHub says receivers should respond within 10 seconds. An integrator should use the limit for the specific provider rather than assume one deadline applies to all webhooks. Shopify’s troubleshooting guidance and GitHub’s best practices give those product-specific examples.
A failed attempt does not mean the event will never arrive. The provider should report the latest attempt’s outcome separately from the event’s current status. It should also show whether another attempt is scheduled, how many attempts have occurred, and when the next one is due.
Retry policies vary by provider. Shopify documents automatic retries for failed calls, up to eight attempts in a four-hour period, and says it stops attempts after that limit. GitHub says it does not automatically redeliver failed deliveries; integrators must request redelivery or arrange their own automation. These examples show why documentation should state the provider’s actual policy rather than imply that every failure leads to another attempt. Shopify’s retry guidance and GitHub’s failed-delivery documentation describe their respective behaviors.
Event IDs and attempt IDs help integrators track retries. An event ID identifies the same event across attempts. A delivery or attempt ID identifies each separate request. When a request times out, the provider may record a failure even though the endpoint received the request. Integrators should check their records before replaying work to avoid duplicate business effects.
“Terminal failure” should say what the provider has stopped: attempts for one event, delivery to an endpoint, or the subscription that sends a category of events. The provider should also say whether the event remains available for manual redelivery or replay, and what the integrator must fix before using that option.
Recovery may involve more than replaying one event. Svix’s replay documentation describes resending one event and replaying failed messages from a selected time. Shopify documents stopping attempts after repeated failures and removing the affected subscription when failures continue. An integrator facing a terminal state should check whether the subscription still exists, fix the endpoint or subscription, and find out whether replay or data reconciliation is available.
A status label rarely explains why delivery stopped. Each delivery record should link the state to the event, attempt, endpoint, and evidence for the outcome. At minimum, providers should show the event ID, attempt number and time, endpoint, response code or transport error, and any scheduled time for the next attempt. They should also identify the event type, exact success criteria, retry policy, terminal condition, and available recovery actions.
Providers should also say how current their records are and how long they keep them. GitHub documents delivery details for the past three days, while Shopify’s dashboard logs cover the past seven days and may appear with a delay. When investigating an incident, integrators need to know whether a record is current and whether it will still be available when they look for it. The GitHub delivery view and Shopify troubleshooting guidance describe these product-specific limits.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com