How to debug a failed webhook
A failed webhook usually comes down to a handful of causes. Work through them in order — status, response body, signature, timeout, idempotency — then replay once you have the fix.
A failed webhook almost always comes down to one of a handful of causes. Rather than guess, work the delivery from the outside in — status first, then the response, then the request — and only replay once you have a fix. This is the order to check things.
The checklist
- Inspect the HTTP status. A
5xxis your handler throwing; a4xxmeans the request was rejected before your logic ran (auth, validation, signature); a timeout means you never returned200in time. The status alone narrows the cause to a category. - Read the response body. Whatever your endpoint returned on failure — an error string, a stack trace, a validation message — is the fastest route to the exact line that broke. Do not skip it; it usually names the problem.
- Check signature verification. If the status is
401or400, the payload probably failed verification. The usual culprits: the wrong signing secret, verifying the parsed body instead of the raw bytes, or clock skew on a timestamped signature. - Rule out a timeout. If there is no response at all, your handler is doing too
much work inline. Acknowledge the webhook fast (return
200) and move slow work to a background job. See webhook 500 errors for the failure-under-load cases. - Check idempotency. Before you consider replaying, confirm your handler tolerates the same event twice — most providers send a stable event or delivery id you can record and de-duplicate on. This step decides whether replay is safe.
- Replay with context. Once the root cause is fixed and deployed, re-send the exact captured payload. Replay one delivery, verify the downstream state changed exactly once, then do the rest. Full detail in replay a failed webhook safely.
- Set alerts. Close the loop so the next failure finds you, not a customer. A Slack or webhook alert on a spike turns silent drops into a message in a channel you already watch.
A worked example
A checkout.session.completed delivery is 500. Response body: TypeError: cannot read 'customer' of undefined. The signature header is present and
valid, so it is not auth. There is one attempt, no prior success, so it is safe to fix and
replay. You patch the handler, deploy, replay the delivery, and confirm the order was created
once. Applied to a real provider, see Stripe webhooks.
Keep reading
Start debugging your webhooks.
Point one endpoint at HookWatch, capture a failure, and replay it once it’s fixed. Free during beta.