The ngrok Problem

If you've built against Stripe, GitHub, or Shopify webhooks, you know the friction: spin up ngrok, get a random URL, paste it into your provider's dashboard, wait for events, debug locally, restart ngrok (new URL), repeat. It works, but it's a loop that kills momentum.

The real issue isn't that you need a tunnel—it's that the tunnel is stateless and ephemeral. Every restart breaks the endpoint. Every new session is manual setup. For teams, sharing a tunnel URL adds complexity. And if you're testing signature verification or replay logic, you're stuck with whatever the provider sends, whenever they send it.

Let's look at what actually works to test webhooks locally, and when each approach makes sense.

Option 1: Local HTTP Server + Manual Testing