How to Test Webhooks Locally Without ngrok
The Problem: Webhooks Need a Public URL
When you are building an integration with Stripe, GitHub, Shopify, or any service that sends webhooks, you hit the same wall every time: your development server runs on localhost:3000, but the webhook sender needs a publicly reachable URL to deliver events to.
Your local machine sits behind a NAT, a firewall, and probably your ISP's CGNAT on top of that. The webhook has no way to reach you.
The Usual Fix: Tunneling
The most common answer is to run a tunnel that exposes your local port to the internet. ngrok is the most popular option, but there are several others:
- ngrok — Free tier gives you a random subdomain. Paid plans for stable URLs. Requires installing a CLI and running it alongside your dev server.
- localtunnel — Open-source npm package. Simple to use, but connections can be unreliable and the shared infrastructure goes down frequently.
- Cloudflare Tunnel — Free, fast, and stable, but requires a Cloudflare account and more configuration. Overkill for quick webhook testing.
- Stripe CLI — Stripe has its own forwarding tool (
stripe listen --forward-to), but it only works for Stripe webhooks. If you also need to test GitHub or Slack webhooks, you need another tool.
Tunnels work, but they add friction. You have to install software, keep a terminal running, update the webhook URL in the sender's dashboard every time the tunnel restarts, and deal with random connection drops. During active development, that friction adds up.
A Different Approach: Capture First, Forward Later
There is another way to think about the problem. Instead of tunneling your local server to the internet, you use a hosted endpoint that captures incoming requests and lets you inspect them immediately. Once you understand the payload shape, headers, and timing, you forward events to your local machine on demand or automatically.
This is the approach HookTest takes. You create a bin (a unique public URL) in one click, point your webhook sender at it, and every request shows up in real-time in your browser. No CLI, no tunnel process, no random subdomains.
Why This Works Better for Development
When you are first building a webhook handler, you usually do not know exactly what the payload looks like. Documentation helps, but real payloads contain fields and edge cases that docs skip over. Being able to see the raw request — headers, body, query parameters, method — before you write any handler code saves a lot of trial and error.
With HookTest, you also get auto-forwarding. Once you have inspected the payload and your handler is ready, you can configure your bin to forward every incoming request to http://localhost:3000/api/webhook or any other URL. The bin stays up even if your local server is down, so you never lose an event. When your server comes back, you can replay missed requests.
When You Might Still Want ngrok
Tunnels are still the right tool when you need the webhook sender to talk directly to your running application in real-time — for example, if the sender expects a specific response body or status code that your handler computes dynamically. For everything else (inspecting payloads, testing event types, debugging signature failures), a capture-and-forward tool is faster and simpler.
Get Started
Create a free bin on HookTest and point your webhook sender at the generated URL. No account required. You will see requests appear in real-time as they come in.
If you are new to webhooks in general, check out What Is a Webhook? A Plain-English Explanation. If you are working with Stripe specifically, see How to Debug Stripe Webhooks.