Webhooks are hard to develop against because the sender is on the internet and your handler is not. The fix is to give your machine a public URL, point the webhook at it, and inspect what actually arrives.
1. Get a public URL
Start a tunnel to your local server — see
exposing localhost to the internet for the
detail. You will end up with something like https://u1-a3f9.schemaclient.com.
2. Register the endpoint
Give the provider your tunnel URL plus your handler's path:
https://u1-a3f9.schemaclient.com/webhooks/stripe
In the Stripe dashboard that is Developers → Webhooks → Add endpoint. GitHub puts it under repository Settings → Webhooks. Slack calls it the Request URL.
Some providers send a verification request the moment you save. If yours does, your
handler must respond 200 to it before the endpoint is enabled.
3. Trigger an event and read the payload
Do the thing that fires the webhook, then open the Traffic Monitor. Every delivery shows up with its headers and body. This is where the useful information lives:
- The signature header —
Stripe-Signature,X-Hub-Signature-256. - The event type, so you can confirm you subscribed to the right one.
- The delivery ID, which providers use for retries and deduplication.
4. Replay instead of re-triggering
Once a delivery is captured you can replay it against your handler. This is the part that saves real time: you stop creating test charges or opening throwaway pull requests just to exercise one code path, and you get a byte-identical payload every run.
5. Handle retries properly
Providers retry on any non-2xx response, so your handler must be idempotent. Store the
delivery ID and ignore repeats. Return 200 as soon as you have persisted the event
and do the slow work afterwards — most providers time out in a few seconds and a
timeout counts as a failure worth retrying.
Once the payload shape is stable, lock it in with schema validation so a provider changing a field does not silently break you.