Testing webhooks locally

Receive Stripe, GitHub or Slack webhooks on your development machine, inspect the payloads, and replay them without re-triggering the event.

Updated

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.

Frequently asked questions

How do I test a webhook without triggering a real event every time?

Capture the first delivery in the traffic monitor, then replay it. Replaying sends the identical payload and headers to your handler as many times as you need, so you can iterate without creating another charge or pull request.

Why does my webhook signature check fail locally?

Signatures are computed over the exact raw request body. If your framework parses the JSON before the signature check runs, the re-serialised body will differ by whitespace or key order and the check fails. Read the raw body first, verify, then parse.