Testing n8n webhooks locally: tunnels and best practices
Published 25 July 2026 · 6 min read
You're building an n8n workflow on your machine, you paste the Webhook node's URL into Stripe's or your CRM's configuration… and nothing ever arrives. Of course: http://localhost:5678 only exists on your computer, and no external service can reach it. This mundane problem costs beginners hours and pushes some people to "just test in production" — the worst possible habit. Here's how to set up a clean local testing loop: test and production URLs, tunnels, the WEBHOOK_URL variable, payload replay, and the checklist before activating anything in production.
The problem: localhost is not reachable from the Internet
A webhook works the opposite way from a classic API call: you don't call the service, the service calls you. For that, it needs a public URL. Yet a development n8n instance typically listens on http://localhost:5678, an address private to your machine — often behind NAT and a firewall on top.
Improvised workarounds are expensive: deploying to the production server after every change just to test means a feedback loop of several minutes where a local test takes seconds. And it's not just about comfort: a study by Meyer, Fritz, Murphy and Zimmermann presented at FSE 2014 ("Software Developers' Perceptions of Productivity" — Google Scholar) shows that developers rate themselves productive on days when tasks move forward without interruptions or context switches — exactly what a "change, deploy, wait, check" cycle destroys. A short local test loop isn't a luxury; it's the precondition for smooth development.
Test URL vs Production URL: the Webhook node's two lives
n8n's Webhook node exposes two distinct URLs, and mixing them up is the number-one source of "it doesn't work":
- Test URL (
webhook-testsegment in the path): it only listens during a manual execution. You click "Execute workflow", n8n waits for an incoming call, processes the first one received while displaying the data right in the editor, then stops listening. Ideal for development: you watch the real payload flow node by node. - Production URL (
webhooksegment): it becomes continuously active once the workflow is activated (the toggle at the top of the editor). Incoming calls trigger executions visible in the executions list, not on the editor canvas.
The practical consequence: a webhook configured on a third-party service with the Test URL will stop responding as soon as you're no longer listening manually. And conversely, a workflow that isn't activated will never respond on its Production URL. Always check which segment (webhook-test or webhook) appears in the URL you pasted into the third-party service.
n8n's built-in tunnel: --tunnel
For quick tests, n8n ships a zero-configuration solution: start the instance with the --tunnel option (for example n8n start --tunnel). n8n then opens an outbound connection to a tunnel service and gives you a public URL forwarded to your local instance; the displayed webhook URLs automatically use that address.
The limitations are by design: traffic transits through a third-party service, the URL isn't yours, and the n8n documentation explicitly reserves this option for local development — never production. It's the perfect tool to confirm in ten minutes that an external service can reach your workflow, not an infrastructure building block.
ngrok and cloudflared: the serious alternatives
When the built-in tunnel is no longer enough (you need to inspect requests, keep a stable URL across sessions, or meet security constraints), two tools dominate:
- ngrok: it opens a tunnel between a public URL and your local port (
ngrok http 5678). Its local dashboard lets you inspect every incoming request — headers, body, response — and replay it with one click, which is invaluable when iterating on payload parsing. On the free tier, the URL changes on every restart; a stable URL requires a paid plan. - cloudflared (Cloudflare Tunnel): the same outbound-connection principle, with an ephemeral mode for quick tests and, if you have a domain managed on Cloudflare, the option of a named tunnel with a stable URL under your own domain.
The choice boils down to: an ephemeral URL that changes every session (fine for a one-off test, painful if you have to re-paste the URL into the third-party service every time) versus a stable URL (essential as soon as development spans several days).
WEBHOOK_URL: making n8n display the right URLs
Behind a tunnel — or later behind a reverse proxy — n8n only knows its local address. Without configuration, it keeps displaying webhook URLs on localhost, unusable as-is. The WEBHOOK_URL environment variable fixes that:
WEBHOOK_URL=https://your-tunnel.example.com/
Once set, n8n generates all its webhook URLs from that base: what you copy from the editor is directly the correct public URL. It's the same variable you'll use in production behind Traefik or Caddy — our guide to HTTPS and a custom domain for n8n covers that setup. Remember to restart the instance after changing it, as with any environment variable.
Replaying payloads without re-triggering the external service
Provoking a real event on the third-party service at every iteration (making another test payment, re-submitting a form) is slow and sometimes impossible. Two techniques free you from it:
- curl: capture a real payload once (from ngrok's inspector or from an n8n execution), save it to a file, and replay it at will:
curl -X POST -H "Content-Type: application/json" -d @payload.json https://…/webhook-test/my-path. You then iterate on the workflow without depending on the external service. - Pinned data: in the n8n editor, you can pin a node's output — including the Webhook's — after a successful run. Subsequent manual executions reuse that frozen data without waiting for a new incoming call: you build the rest of the workflow in peace. Pinned data only applies to manual executions, never in production — no risk of a test dataset leaking into real runs.
This combination — one real payload captured once, then replayed in a loop — gives you the shortest possible feedback loop, the very thing the FSE study cited above identifies as a direct driver of perceived productivity.
Checklist before going to production
The tunnel proved the workflow works; before exposing the webhook for real, three non-negotiable steps:
- HTTPS on your own domain: no more development tunnel, but a reverse proxy with a TLS certificate (Traefik / Caddy guide). A plain-HTTP webhook exposes payloads — and many services simply refuse non-HTTPS URLs.
- Webhook authentication: a production URL is public and will be scanned. Authentication headers, HMAC signature verification when the service offers it: our article on securing an n8n webhook reviews the node's options.
- Idempotency: external services resend webhooks whenever in doubt (timeout, network error), and the same event can arrive twice. Without a safeguard, that's the invoice sent in duplicate. The deduplication mechanism is detailed in webhook idempotency: avoiding duplicates.
- Error handling: an Error Workflow so you're alerted when a webhook-triggered execution fails silently.
If you're still weighing self-hosting this instance against n8n Cloud, our self-hosted vs cloud comparison lays out the criteria — webhook and public URL management weighs heavily in it.
In short
Testing webhooks locally is straightforward once the pieces are in place: the Test URL to watch payloads in the editor, a tunnel (--tunnel as a quick fix, ngrok or cloudflared for real work), WEBHOOK_URL so the displayed URLs are the right ones, and pinned or curl-replayed payloads to iterate fast. This rigor pays off directly on the webhook-triggered workflows in the FlowKit packs — the email and form intake of the Inbox AI Pack (€79), the RAG question-answering API, or the document submissions of the Compliance & Audit Pack (€149) — all shipped with their webhooks ready to lock down using the checklist above.
FAQ
Frequently asked questions
Why is my local n8n webhook not receiving anything?
Because an external service (Stripe, GitHub, a CRM…) cannot reach http://localhost:5678: that address only exists on your machine. You need to expose your local instance through a tunnel — n8n's --tunnel option for a quick test, or ngrok / cloudflared — which provides a public URL forwarded to your local n8n.
What's the difference between the Webhook node's Test URL and Production URL?
The Test URL (with the webhook-test segment) only listens during a manual execution: you click Execute workflow, n8n waits for a call, processes it while showing the data in the editor, then stops listening. The Production URL (webhook segment) becomes continuously active once the workflow is activated, and its runs show up in the executions list, not in the editor.
Can I use n8n's built-in tunnel (--tunnel) in production?
No. The --tunnel option routes traffic through a third-party service operated for development, with a URL you don't control: n8n documents it as intended for local development only and explicitly advises against any production use. For production, expose the instance behind your own domain over HTTPS with a reverse proxy.
What is the WEBHOOK_URL variable for?
It tells n8n the public base URL to use when generating its webhook addresses. Behind a tunnel or a reverse proxy, n8n only knows its local address: without WEBHOOK_URL, it displays localhost URLs that external services cannot use. Set it to the tunnel's or domain's public URL and the displayed URLs become correct.
Bundle FlowKit Complet
€269