The problem
Webhook providers need a public HTTPS URL. Your development server runs on localhost:3000, which nothing on the internet can reach. There are three good ways around that, and they work best together.
1. Use the provider's CLI, if it has one
Some providers ship a tool that receives events for you and forwards them to localhost. Stripe's CLI is the best known:
stripe listen --forward-to localhost:3000/api/webhooks/stripe
stripe trigger invoice.paidstripe listen prints its own signing secret (whsec_…). Use that one locally: it is different from your dashboard endpoint's secret, and mixing them up is the most common cause of Stripe signature errors.
2. Open a tunnel
A tunnel gives your local port a temporary public URL. Popular options are cloudflared (cloudflared tunnel --url http://localhost:3000) and ngrok (ngrok http 3000). Paste the URL they print into the provider's webhook settings.
Tunnels are fine for development, but the URL changes when you restart them, and every restart means updating the provider. Never leave a tunnel pointing at a machine with real data.
3. Capture once, replay as often as you like
The fastest feedback loop is to record real events once and replay them against your handler while you change the code. That also lets you test the awkward cases: the event that arrived out of order, the payload with a missing field, the duplicate.
With HookStead:
- Point the provider at a HookStead source. Every event is stored, even before you have an endpoint.
- Start a tunnel to your machine and add its URL as a destination.
- Open any stored event in the dashboard and replay it to that destination, as often as you need. Each replay is signed fresh, so your local verification code is exercised too.
- When you're done, pause or delete that destination. The events stay, and production keeps working.
HookStead only delivers to public addresses (private and local IP ranges are blocked to prevent SSRF), so a tunnel is the way to reach your laptop.
What to test before you ship
- Signature verification fails closed: a request with a wrong or missing signature gets a 400, and nothing happens.
- The raw body reaches your verifier, not a parsed and re-serialized one. See Webhook security.
- Duplicates are harmless: replay the same event twice and check that nothing is done twice. See Retries and idempotency.
- Slow work happens in the background, and the handler answers in well under a second.
- Unknown event types are ignored with a 2xx, so a new event type from the provider doesn't cause retries.
Next: set up your first source with the user guide, or read about what a webhook is.