The Problem: Your Handler Is Local, the Provider Isn't
You're building a Stripe integration. Your Express server runs at localhost:3000. Stripe needs to POST payment events to a public HTTPS URL. Your laptop is not publicly reachable from Stripe's servers.
The standard solution for years has been ngrok — a tunnel that exposes a port on your local machine to the internet. It works, but it has real costs:
- CLI binary to install and update — one more tool in the chain
- Persistent terminal process — the tunnel dies when you close the terminal
- Rotating URLs on free plan — every restart gives you a new URL, meaning you have to update Stripe, GitHub, Shopify, etc. every time
- Exposes your whole local server — ngrok tunnels the entire port, not just the webhook path
- No event persistence — if your server was down when an event arrived, it's gone
There's an alternative that doesn't have any of these problems.
How Cloud-Side Forwarding Works
Instead of exposing your local machine to the internet, a cloud-side relay works like this:
- A permanent public HTTPS URL lives in the cloud (e.g.
https://webhookwhisper.com/hook/abc123) - You register this URL with your provider — Stripe, GitHub, Shopify — once, permanently
- You configure a forwarding rule: when an event arrives, relay it to
http://localhost:3000/webhooks/stripe - The cloud server makes the outbound request to your localhost — this works because the cloud is making a request to you, not the other way around
Your machine receives the incoming HTTP POST just like it would from the provider directly. All original headers are preserved — including signature headers like Stripe-Signature (see Stripe webhook testing guide) and X-Hub-Signature-256 (see GitHub webhook testing guide) — so signature verification works identically.
The cloud URL is permanent. You register it once and never touch it again. No tunnel to keep running, no URL rotation, no persistent CLI process.
Step-by-Step: Forward Webhooks to Localhost With WebhookWhisper
Step 1 — Create a free public endpoint
Go to webhookwhisper.com and click Get my free webhook URL. You get a unique HTTPS URL like https://webhookwhisper.com/hook/abc123xyz instantly — no account, no email, no CLI.
If you want a permanent URL that doesn't expire (recommended for any integration you're actively developing), sign up for a free account and create a named endpoint under the Dashboard.
Step 2 — Register the URL with your provider
Paste your WebhookWhisper URL wherever your provider expects a webhook endpoint — see the full provider directory for 35+ providers:
- Stripe: Developers → Webhooks → Add endpoint
- GitHub: Settings → Webhooks → Add webhook
- Shopify: Settings → Notifications → Webhooks → Create webhook
- Any other provider: wherever you'd normally paste a webhook URL
This is the only time you touch the provider settings. The URL never changes.
Step 3 — Set a forwarding rule
- In your WebhookWhisper endpoint, open the Forwarding tab
- Click Add rule
- Enter your local handler URL as the target:
http://localhost:3000/webhooks/stripe - Click Save
That's it. Now every event that arrives at your WebhookWhisper URL is automatically relayed to your local server.
Step 4 — Run your local server and test
Start your local server as usual (npm run dev, python manage.py runserver, whatever your stack uses). Fire a test event from your provider's dashboard. Watch it arrive in the WebhookWhisper inspector and simultaneously hit your local handler.
The delivery log shows the HTTP status your handler returned, the response body, and round-trip time — so you can see instantly whether your handler is processing the event correctly.
What Happens When Your Local Server Is Down
This is where the approach beats ngrok significantly. With ngrok, if your tunnel is down when an event arrives, the event is lost. The provider's retry logic eventually retries, but that might be minutes or hours later.
With WebhookWhisper:
- The event is received and stored regardless of whether your local server is running
- The forwarding attempt is logged as failed with the error details
- WebhookWhisper retries automatically using exponential backoff
- You can also manually replay any past event from the dashboard once your server is back up
This means restarting your local server mid-development doesn't cause missed events. You close the terminal, fix a bug, restart the server, and replay the last event — all from the browser.
Signature Verification Still Works
A common concern: if the request is being proxied through WebhookWhisper, will the signature header still be valid?
Yes — because WebhookWhisper forwards all original request headers intact. The Stripe-Signature header that Stripe included when the event was sent to your WebhookWhisper URL is passed through verbatim to your local server. The raw body is also preserved byte-for-byte. So stripe.webhooks.constructEvent(rawBody, sig, secret) works exactly as it does in production.
Same for GitHub's X-Hub-Signature-256 and Shopify's X-Shopify-Hmac-Sha256 — they're all forwarded intact.
ngrok vs WebhookWhisper — Side-by-Side
| Feature | ngrok (free) | WebhookWhisper |
|---|---|---|
| Installation required | Yes — binary + auth token | No — browser only |
| Persistent terminal process | Yes | No |
| Static URL | No (rotates each restart) | Yes |
| Event stored if server is down | No | Yes |
| Automatic retry on failure | No | Yes |
| Event replay | No | Yes |
| Delivery log with HTTP status | No | Yes |
| Exposes entire local port | Yes | No — only the target URL |
| Multiple forwarding targets | No | Yes |
| Free tier forwarding | Yes (rotating URL) | Yes (50 events, static URL on paid) |
Common Configurations
Forward to a specific path
Set the forwarding target to the exact handler path your app expects:
http://localhost:3000/webhooks/stripe
http://localhost:8080/api/github/events
http://localhost:5000/shopify/orders
Forward to multiple targets simultaneously
Add multiple forwarding rules on the same endpoint. Useful for testing locally while also hitting a staging environment:
Target 1: http://localhost:3000/webhooks/stripe (your local dev server)
Target 2: https://staging.yourapp.com/webhooks/stripe (staging)
Docker or WSL networks
If your handler runs in Docker or WSL2, localhost may not resolve correctly from the forwarding server. Use the host machine's IP or Docker's host gateway instead:
http://host.docker.internal:3000/webhooks/stripe (Docker on Mac/Windows)
http://172.17.0.1:3000/webhooks/stripe (Docker on Linux)
http://192.168.1.5:3000/webhooks/stripe (your machine's LAN IP)
Related Guides
- How to Test Stripe Webhooks Without Deploying
- How to Test GitHub Webhooks Locally
- How to Debug Webhooks: A Practical Guide
- Webhook Forwarding — Free Tool
- 35+ Webhook Providers — Events & Payload Reference
Summary
You don't need ngrok to forward webhooks to localhost. A cloud-side relay like WebhookWhisper gives you a permanent public HTTPS URL, relays events to your local handler with all headers intact, retries on failure, and logs every delivery attempt — with no binary to install and no terminal to keep running.
The forwarding-to-localhost workflow with WebhookWhisper:
- Create a free endpoint at webhookwhisper.com
- Register it with your provider (Stripe, GitHub, Shopify, etc.) — once
- Add a forwarding rule: target =
http://localhost:3000/your-path - Run your local server and receive events in real time
Create your free account and have your first webhook forwarding to localhost in under two minutes.