The Problem Every Webhook Developer Faces
You've written a webhook handler. It looks right. But there's no way to know it actually works until a real provider — Stripe, GitHub, Shopify, Slack — sends an HTTP POST to a publicly reachable URL. And that URL is not localhost:3000.
This is the webhook testing problem. Your code is local. The provider is not. Something has to bridge the gap.
This guide covers every approach to testing webhooks locally in 2026 — from the quick-and-dirty to the production-ready — and helps you pick the right one for your situation.
Option 1 — Cloud Relay (Recommended for Most Developers)
A cloud relay gives you a permanent public HTTPS URL that forwards incoming webhook events to your local server. No tunnel, no binary to install, no rotating URLs.
How it works:
- You get a permanent public URL (e.g.
https://webhookwhisper.com/hook/abc123) - You register that URL with your provider — Stripe, GitHub, Shopify — once
- You set a forwarding rule: relay all incoming events to
http://localhost:3000/webhooks - Events arrive at your local handler with all original headers intact
Why this beats ngrok: The public URL never changes. You register it once and forget it. Your local server can restart freely without breaking the integration. If your server is down, events are queued and retried. The full payload and headers are logged so you can inspect and replay any event.
How to set it up with WebhookWhisper:
- Go to webhookwhisper.com and click Get my free webhook URL
- Register the URL with your provider (see provider-specific guides below)
- Open the Forwarding tab, add a rule: target =
http://localhost:3000/webhooks - Fire a test event — it arrives at your handler in real time
Works for every major provider: Stripe, GitHub, Shopify, Slack, and 35+ more.
Option 2 — ngrok (The Classic Tunnel)
ngrok creates a tunnel from a public URL to a port on your machine. It's the most widely known solution, but has real friction:
- Rotating URLs on free plan — every restart gives you a new URL, meaning you must re-register the webhook in every provider
- Persistent terminal process — the tunnel dies when you close the window
- Binary to install and update — one more tool in the chain
- Exposes your whole local port — not just the webhook path
- No event persistence — if your server was down when an event arrived, it's gone
ngrok is fine for a quick one-off test. It's painful for day-to-day webhook development. See our full webhook forwarding guide for a direct comparison.
# ngrok basic usage
ngrok http 3000
# Your public URL (changes every restart on free plan):
# https://a1b2c3d4.ngrok.io → http://localhost:3000
Option 3 — Provider CLI Tools
Some providers ship their own CLI for local webhook testing:
Stripe CLI
# Install
brew install stripe/stripe-cli/stripe
# Authenticate
stripe login
# Forward to localhost
stripe listen --forward-to localhost:3000/webhooks/stripe
Works well but only for Stripe events. Requires auth, a running terminal process, and the Stripe CLI binary. The signing secret it uses is CLI-specific — different from your Dashboard endpoint secret. Read our Stripe webhook testing guide for the full setup.
GitHub CLI / Smee.io
GitHub doesn't ship a native forwarding CLI. The community tool is smee.io — a free proxy that uses Server-Sent Events. It works but has the same rotating URL problem as ngrok free tier.
Option 4 — Fire Test Payloads Directly (No Real Provider Needed)
For many webhook handler tests, you don't need a real event from the provider at all. You just need a realistic payload hitting your endpoint. There are two ways to do this:
WebhookWhisper Test Sender (No account needed)
Go to any provider page in our webhook provider directory, select an event type, enter your localhost URL, and click Send. A realistic payload with proper headers fires at your endpoint from our servers — bypassing the browser CORS restriction and reaching your local handler directly.
Works for all 35+ providers without any account or provider credentials. See it in action:
- Stripe Webhook Testing — Fire test payloads instantly
- GitHub Webhook Testing
- Shopify Webhook Testing
curl / Postman
For simple handlers, you can POST a crafted payload directly:
curl -X POST http://localhost:3000/webhooks/stripe \
-H "Content-Type: application/json" \
-H "Stripe-Signature: t=1712000000,v1=test" \
-d '{"type":"payment_intent.succeeded","data":{"object":{"amount":2999}}}'
Limitation: the signature won't verify (since you don't know the signing secret for a locally crafted payload). Use this only for testing handler routing logic, not signature verification.
Provider-Specific Guides
Each provider has quirks — different signature headers, different retry policies, different event formats. Use these guides for provider-specific setup:
| Provider | Guide | Key Header |
|---|---|---|
| Stripe | Testing guide · Signature verification | Stripe-Signature |
| GitHub | Testing guide | X-Hub-Signature-256 |
| Shopify | Testing guide | X-Shopify-Hmac-Sha256 |
| Slack | Events reference | X-Slack-Signature |
| 35+ more | Provider directory | Varies |
Verifying Webhook Signatures Locally
Most providers sign their webhooks with HMAC-SHA256. Verifying the signature locally is the same as in production — your signing secret doesn't change based on environment. The only difference is where the event comes from.
The critical rule across all providers: use the raw request body, never the parsed JSON object. In Node.js:
// Express — webhook route must use raw body middleware
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
// req.body is a Buffer here — pass directly to signature verification
const isValid = verifySignature(req.body, req.headers)
if (!isValid) return res.status(401).send('Unauthorized')
const payload = JSON.parse(req.body) // parse AFTER verification
// handle event...
res.json({ received: true })
})
For provider-specific signature verification code, see our Stripe signature verification guide (covers Node.js, Python, and Go).
Debugging Webhooks That Fail Locally
When your local handler returns an error or your test event never arrives, follow this checklist:
- Check the provider's delivery log first — did the event leave the provider? Stripe, GitHub, and Shopify all have delivery attempt logs in their dashboards.
- Check WebhookWhisper's event log — did it arrive at the public endpoint? If yes and your handler didn't receive it, the forwarding rule is the issue.
- Check your local server is running — it sounds obvious but forwarding silently fails if nothing is listening on the target port.
- Check the response code — the delivery log shows what HTTP status your handler returned. A 500 means your code crashed; a 401 usually means signature verification failed.
- Replay the event — fix the bug, click Replay in WebhookWhisper, confirm the handler now returns 200.
For a deep dive into each failure mode, read How to Debug Webhooks: A Practical Guide.
Choosing the Right Approach
| Situation | Best Option |
|---|---|
| Day-to-day webhook development with any provider | Cloud relay (WebhookWhisper) |
| Quick one-off test, don't mind rotating URL | ngrok free tier |
| Stripe-only integration, comfortable with CLI | Stripe CLI listen |
| Testing handler logic without real provider | Test Sender or curl |
| Testing signature verification end-to-end | Cloud relay + Dashboard "Send test event" |
| System behind firewall, can't receive inbound | Polling + reconciliation job |
Summary
Testing webhooks locally comes down to one problem: your handler is not publicly reachable. The solutions, in order of recommendation:
- Cloud relay (WebhookWhisper) — permanent URL, full logging, forwarding with retry, works for all 35+ providers
- ngrok — fast to start, rotating URL is friction for daily use
- Provider CLI — only works for one provider at a time
- Direct curl/Test Sender — for handler logic tests without a real provider event
Related Guides
- Forward Webhooks to Localhost Without ngrok — Full Guide
- How to Debug Webhooks: A Practical Guide
- How to Test Stripe Webhooks Without Deploying
- How to Test GitHub Webhooks Locally
- How to Test Shopify Webhooks Locally
- Stripe Webhook Signature Verification Guide
- 35+ Webhook Provider Directory
- Webhook Forwarding — Free Tool