Before you build signature verification, idempotency, and retry handling, you often just need to see what a webhook actually sends. A permanent inspection endpoint answers that question faster than a tunnel and a temporary server.
Most webhook tutorials skip the first question you actually have. They show you how to verify a signature, parse the body, and enqueue the event for processing. All of that assumes you already know what the request looks like. In practice, the very first thing you need is dumber and more important: what does this provider actually send to my endpoint?
The documentation gives you an idealized version. A clean JSON sample, a list of headers, an event type field sitting exactly where you expect it. Then the real request arrives and the structure is nested two levels deeper than the docs implied, the event type is split across a header and a body field, and there is a timestamp you need for signature validation that nobody mentioned. The gap between the documented payload and the delivered payload is where a lot of integration time disappears.

The problem with the usual first step
The standard advice for seeing a real webhook is to stand up a local server, expose it with a tunneling tool like ngrok, write a throwaway handler that logs the request, format the output so it is readable, and then trigger a test event from the provider. This works. It is also a surprising amount of scaffolding for a question that is essentially read-only.
Think about what you are really doing in that workflow. You are provisioning a public ingress path, running a process that has to stay alive, and writing code whose entire purpose is to be deleted in ten minutes. Every one of those steps can fail independently. The tunnel session expires. The local port is already in use. Your log formatting swallows a header you needed. You restart the process and lose the one delivery you were trying to capture. None of this is hard, but all of it is friction stacked in front of a simple observation.
There is also a subtler issue. When you write a handler to inspect a payload, you immediately start making decisions about how to parse it. You pick fields to log. You assume a content type. You quietly bake in expectations before you have seen the evidence. That defeats the purpose of looking.
A permanent endpoint as a capture point
Adal takes a different approach: it gives you a fixed webhook URL that just receives and stores whatever shows up. You paste that URL into the webhook settings of Stripe, GitHub, Slack, Dodo Payments, or any provider that posts events, fire a test event, and the raw request is captured and displayed. Method, path, headers, body, payload structure, request size, and delivery details are all visible without you writing anything.

The architectural value here is separation of concerns. Capture and processing are two different jobs with two different reliability profiles. Capture needs to accept any request, never reject it, and persist it durably so you can look at it later. Processing needs validation, idempotency, and business logic. Conflating them in a throwaway handler means your inspection tool inherits all the fragility of application code. Splitting them means the thing that records the request has exactly one responsibility and almost nothing to get wrong.
This matters most when the event you care about is rare or hard to reproduce. A failed payment, a specific GitHub event, a webhook that only fires under a particular account state. You want a capture point that was already listening when the event happened, not a process you spun up after the fact and hope to keep running until the next occurrence.
Where this fits in debugging
The inspection use case extends naturally into debugging, and this is where having the raw request matters most. When a webhook integration breaks, the failure is often not in your handler at all. The common culprits are upstream of your code:
- The provider is posting to the wrong URL, so your handler never runs.
- The event subscription does not match the event you expected, so the payload is for something else entirely.
- A header you depend on for routing or verification is absent.
- The signature is computed over a different canonical form than your verification code assumes.
Without the raw request in front of you, every one of these looks like a bug in your application. You add logging, redeploy, and stare at code that is working correctly. With the raw request visible, the diagnosis is usually immediate. You compare what arrived against what the docs promised and the mismatch is right there. The distinction between "my handler is wrong" and "the request never matched my assumptions" is the difference between an hour and a minute.
The trade-offs, stated plainly
An inspection endpoint does not replace real webhook processing, and treating it as if it does would be a mistake. Production workflows still need signature verification to reject forged requests, idempotency keys to survive duplicate deliveries, retry handling for transient failures, and the actual business logic that responds to the event. A capture-and-display tool deliberately does none of that. It accepts everything, which is exactly what you do not want a production endpoint to do.
There is also a data consideration worth being honest about. Sending real webhook traffic to a third-party inspection service means that payload now lives somewhere outside your infrastructure. For test events that is fine. For production traffic carrying customer data, route it through your own systems and use the inspection endpoint only for the test deliveries that providers let you trigger on demand.

The right mental model is staging. The inspection endpoint is where you go to answer "what does this thing send" before you have committed any code to handling it. Once you know the method, the headers, the body shape, and where the event type and signature live, you write the real handler against evidence instead of against the documentation's best guess. Then you point the provider at your own backend and the inspection endpoint goes back to being a tool you reach for the next time something arrives that does not look the way you expected.
The broader pattern is one that distributed systems work teaches repeatedly: observation should be cheap and decoupled from the system being observed. You do not want to deploy code to find out what your dependencies are doing. The faster and lower-friction the path from "I wonder what this sends" to "here is the exact request," the fewer wrong assumptions make it into your handler in the first place.

Comments
Please log in or register to join the discussion