Webhook Debugging
A webhook can die in four places, and each one leaves a different fingerprint. If you can't tell which stage failed, you can't fix the right thing — you re-fire test events and hope the failure reproduces, while the original event that actually broke is gone.
The Four Places a Webhook Dies
1. Never sent. The sender didn't dispatch it. Cause is on their side: a disabled endpoint, a filtered event type, an incident on their platform. Fingerprint: their dashboard shows no delivery attempt at all.
2. Rejected at ingress. The request reached your edge but was refused. Signature mismatch, IP allowlist, TLS failure, payload too large. Fingerprint: the sender records a 4xx/5xx immediately, before any of your handler code runs.
3. Transform failed. The payload arrived, was accepted, and normalization threw — a missing field, an unexpected type, a null where a string was required. Fingerprint: ingress succeeded, but nothing downstream ever saw the event.
4. Destination dropped it. The transformed event was delivered to your service, which returned 200 — but the business logic silently no-op'd. A missing if branch, a swallowed exception, a queue write that never happened. Fingerprint: the delivery log says success; the database says nothing happened.
Each fingerprint requires different evidence to confirm. Without it, all four failures look identical from the outside: "the webhook didn't work."
Why console.log Archaeology Fails
Raw endpoints keep no history. Your options are:
- Ask the sender to re-fire — assumes their dashboard even offers that, and assumes the same conditions reproduce the bug.
- Read live logs — assumes you were logging the right thing when the bad event first arrived.
- Add logging, wait for the next occurrence — assumes the failure is frequent enough to catch again.
None of that helps with the event that already broke. The payload, the transform result, and the response your service returned are gone.
Debugging From Stored Evidence
The systematic answer is to put a gateway in front that stores each stage's evidence per-event, with no code from you:
- Raw payload — the bytes as they arrived, with headers. Rules out "the sender never sent it" or "the payload was mangled in transit."
- Signature result — pass/fail on the ingress check. Confirms or clears "rejected at ingress."
- Transform result — the output of your Golden Model, or the error it threw. Confirms or clears "transform failed."
- Delivery attempts — every attempt to your endpoint, with response code, body, and timing. Confirms or clears "delivered and dropped."
- Dead Letter Queue — events that failed at any stage, held with full payload fidelity until you fix the cause.
Slashbin's Console stores all of the above for every event. You open the delivery log, filter to the failing event, and walk the stages until one turns red. The stage that failed is the bug.
Worked Example
For a specific integration, see Debug Stripe Webhooks — the same four-stage walk applied to Stripe events, with a fix-then-replay loop you can run without asking Stripe to re-send anything.
Fix, Then Replay
Once you know which stage failed and you've shipped the fix, you don't need the original sender to re-fire. The raw payload is stored — Replay re-runs it through the current transform and delivery pipeline. The failing event that started your investigation is the same event you re-drive to verify the fix.
Not the same as a capture tool
Instant-inspection tools like webhook.site are excellent for the first minute — "did the vendor even send anything, and what does the payload look like?" They're a scratchpad. Once the integration is real, the debugging problem changes: you need stored evidence for events that already happened, retries you didn't have to write, and replay against the current pipeline. See Slashbin vs webhook.site for the shape of that boundary, or all comparisons for the same boundary drawn against the other tools in this space.