Building a Webhook Incident Runbook
A webhook is a message that a provider sends to your service when an event occurs, such as a payment update or…
A webhook is an event notification that one service sends to another as an HTTP request. When a provider sends an event to a service behind a proxy, the request passes through one or more intermediaries before it reaches the code that handles it. A proxy accepts traffic for another server and forwards requests and responses between the server and the provider. A content delivery network (CDN) or load balancer can also sit in this path.
Each intermediary uses its own connection, timers, and logs. The provider can report a timeout even when the service finishes processing later. A proxy can also send a 5xx error status that the service did not send. To find the failure, follow the request from the provider through each intermediary to the service, then follow the response back. The provider’s delivery record shows what the provider observed. Proxy and service logs show where the request or response changed or stalled.
A typical path is:
Webhook provider → edge proxy or CDN → load balancer or reverse proxy → service
The provider decides whether delivery succeeded based on the response it receives and whether it arrives before the provider’s deadline. The service’s successful completion does not prove the provider received that response. An intermediary can time out or close the connection first.
Start by identifying which component handled each part of the exchange. For a single delivery, compare the provider’s delivery record with the proxy’s access log and the service’s request and response logs. Match the records by the provider’s delivery ID or another request identifier and by timestamp.
Webhook providers set their own rules for what counts as a successful delivery. GitHub, for example, expects a 2xx response within 10 seconds and records a slower response as a failure. Its webhook troubleshooting guidance recommends queuing work so the service can acknowledge the request promptly and process the event afterward.
A proxy adds separate timers. NGINX’s default proxy_read_timeout is 60 seconds between successive reads from the upstream server, meaning the server to which NGINX forwards the request. It is not a limit on the total duration of every request. If the upstream sends data during the interval, the exchange can last longer; if it sends nothing for the configured interval, NGINX closes the connection. The NGINX proxy documentation describes this timer and its other proxy settings.
Other proxy services and load balancers use timers that measure different conditions. Cloudflare documents a default 125-second Proxy Read Timeout for its 524 error, while an AWS Application Load Balancer has a documented default 60-second connection idle timeout. These timers measure different things, so their values are not interchangeable. Compare what each timer measures, not just its duration.
A handler that waits on database writes or another service before acknowledging an event can exceed the provider’s deadline while remaining within a proxy’s timeout. Conversely, an intermediary can time out before the provider’s own deadline. When either component closes its connection, the provider may record a failure even if the service later finishes its work.
Request-body buffering determines when the service starts receiving the webhook. With NGINX’s default request buffering, the proxy reads the full request body before forwarding it upstream. With buffering disabled, it forwards the body as it arrives. A slow or large request can therefore take longer to reach the service when buffering is enabled.
Response buffering controls how the proxy handles data coming back from the service. NGINX reads an upstream response and stores it in memory buffers; it can write some data to a temporary file if the buffers are insufficient. With response buffering disabled, NGINX passes the response to the client as it receives it. Buffering changes the flow, but it does not establish that the proxy always waits for the full response before forwarding any part of it. Check the proxy configuration and timestamps before attributing an acknowledgement delay to buffering.
For a suspected delay, compare when the provider began sending the request, when the proxy received it, when the service received the body, and when the service sent response headers. Those points help distinguish a slow upload from a slow handler or a delayed response path.
The provider’s status code describes the response it received. It does not, by itself, identify which component produced that response. Under HTTP semantics, a 502 means a gateway or proxy received an invalid response from an upstream server; a 504 means it did not receive a timely response. A proxy can send either status when the service connection fails, even if the service did not deliberately return that code.
Intermediaries can also handle or replace upstream responses according to configuration. For example, NGINX can intercept proxied responses with status codes of 300 or higher and pass them to its error-page handling. Treat a 5xx in the provider dashboard as a clue. It does not prove that the service returned that status.
Compare the status sent to the provider with the upstream status recorded by the proxy and the status logged by the service. AWS Application Load Balancer access logs, for example, distinguish the load balancer’s elb_status_code from the target’s target_status_code, and record separate processing times. That distinction can show whether the target responded and whether the load balancer sent a different result.
Start with the provider’s records, then follow the request toward the service:
The failure stage matters. The proxy can fail to connect to the service, or it can connect and then wait too long for the service to send a response. Cloudflare’s documentation, for instance, separates connection-handshake and request-acknowledgement timeouts for its 522 error. Those thresholds describe Cloudflare behavior, not universal network limits.
Sender retry policies also differ, so a failed attempt does not predict what happens next. PayPal documents retries for specified delivery failures, while GitHub Enterprise Cloud says failed deliveries are not automatically redelivered. Check the relevant provider’s retry guidance or failed-delivery documentation before relying on retries for recovery.
When the provider requires a quick response, the service can handle receipt separately from longer-running work. It can validate the request, place the event in a queue, and return a success response. A background worker can then take the event from the queue and finish processing it. The provider’s documentation sets the response deadline and defines which status counts as success. The service’s logs should show whether it accepted the event for later processing.
This approach reduces how long the provider waits for the service to finish handling the request, but you still need to check the proxy. Confirm that the proxy forwards the request body as expected and sends the success response back to the provider.
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