Short answer: For a throwaway webhook test, Cloudflare's quick tunnel is the lowest-friction option because it needs no account. For a URL you register once with Stripe, GitHub, or Slack and keep for months, you want a stable hostname: ngrok with a reserved domain, a named Cloudflare Tunnel, or Tailscale Funnel if your machine is already on a tailnet. The deciding factors are URL stability, what the tunnel does to your requests, and whether you already own a domain.
All three behaviors below are as of October 2026. Tunnel vendors change free-tier terms often, so check the current docs before you commit.
What problem are you actually solving?
A webhook provider needs a public HTTPS URL. Your handler runs on localhost:3000. A tunnel gives you a public hostname that forwards to that port.
Before comparing tools, start with a handler that shows you what really arrives. Most "the tunnel is broken" reports I have debugged turned out to be the handler, not the tunnel.
// server.js: run with: node server.js
import http from "node:http";
http.createServer((req, res) => {
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
const body = Buffer.concat(chunks);
console.log(req.method, req.url, `${body.length} bytes`);
console.log("headers:", req.headers);
res.writeHead(200, { "content-type": "text/plain" });
res.end("ok");
});
}).listen(3000, () => console.log("listening on :3000"));
Once this prints a request through your tunnel, you know the path works end to end. Then swap in your real handler.
Takeaway: prove the tunnel with a dumb echo server first, so you never debug your handler and your tunnel at the same time.
How do the three compare?
| ngrok | Cloudflare Tunnel | Tailscale Funnel | |
|---|---|---|---|
| Account needed | Yes | Quick tunnel: no. Named tunnel: yes, plus a domain on Cloudflare | Yes, plus a tailnet |
| Throwaway URL | Random, changes per session unless you reserve a domain |
trycloudflare.com random URL, changes every run |
Stable per machine name |
| Stable URL, free | Typically one static domain on the free plan (verify current terms) | Yes, with a named tunnel on your own domain | Yes, <machine>.<tailnet>.ts.net
|
| Request inspector / replay | Built in, the main reason people pick it | None built in | None built in |
| Ports | Any local port | Any local port | Public side limited to a few ports (443, 8443, 10000) |
| Main gotcha | Free-tier browser interstitial page | Quick tunnels are for testing, with documented limits | Needs HTTPS and MagicDNS enabled on the tailnet |
When is ngrok the right pick?
ngrok's real advantage is the request inspector. You open its local web UI, see every incoming webhook with headers and body, and replay one with a click. When a provider sends a payload once and your handler crashed on it, replay saves you from re-triggering the event. For debugging signature verification, that matters; if you are fighting a failing signature check, being able to replay the exact bytes is worth a lot.
ngrok http 3000
# with a reserved domain from your dashboard:
ngrok http --url=your-name.ngrok-free.app 3000
The flag name for the domain has changed between ngrok CLI versions, so run ngrok http --help rather than trusting a blog post, including this one.
Limitations I have hit: the free plan can show an interstitial warning page to browser traffic. Webhook providers are not browsers, so they are usually unaffected, but if you test with a browser or a tool that sends a browser-like user agent, you may see the interstitial instead of your app. Also, your traffic and your inspector history go through a third party, which matters if payloads contain real customer data.
If you want the managed version of this with request replay built in, ngrok is the one that gives you inspection and a stable domain without owning DNS.
Takeaway: pick ngrok when you need to replay and inspect payloads, and accept that your webhook traffic transits its service.
When does Cloudflare Tunnel win?
Two modes, and people conflate them.
# Quick tunnel: no account, random URL, dies when the process stops
cloudflared tunnel --url http://localhost:3000
# Named tunnel: stable hostname on a domain you manage in Cloudflare
cloudflared tunnel login
cloudflared tunnel create dev-webhooks
cloudflared tunnel route dns dev-webhooks hooks.example.com
cloudflared tunnel run --url http://localhost:3000 dev-webhooks
The quick tunnel is the fastest path from zero to a working public URL. Cloudflare documents it as a testing feature with limits, and as of my last check it does not support Server-Sent Events, so if your handler streams, test with a named tunnel instead. The URL changes every run, which means re-registering the webhook each time. Fine for a ten-minute experiment, painful for a week of development.
The named tunnel gives you a permanent hostname, but only if your domain's DNS is on Cloudflare. If it is not, you are signing up for a DNS migration just to test a webhook. If you already have a domain there, Cloudflare Tunnel is the one that gives you a permanent hostname at no extra cost and with no third-party hostname in your provider's settings.
Drawback: there is no built-in request inspector. You add logging to your own handler, which is why the echo server above is useful.
Takeaway: Cloudflare Tunnel is the best stable free option only if your domain already lives in Cloudflare; otherwise use the quick tunnel for short tests.
Is Tailscale Funnel a good fit for webhooks?
If your laptop or home server is already on a tailnet, it is nearly free to set up.
tailscale funnel 3000
tailscale funnel status
You get https://<machine>.<tailnet>.ts.net, stable across sessions, with a valid certificate. You must have HTTPS certificates and MagicDNS enabled for the tailnet, and Funnel has to be allowed in the access policy; the first run prints what is missing.
The catch is exposure model. Funnel makes a service on your machine reachable from the public internet, and bandwidth limits exist that you cannot configure. The public ports are restricted to a short list, so you cannot pick an arbitrary one. For a webhook receiver this is acceptable; for anything heavier, it is not the tool.
Because the hostname contains your tailnet name, you are also publishing that name in whatever dashboard you register it in. Some people do not want that in a shared Stripe account.
Takeaway: Funnel is the right answer when you are already a Tailscale user and want a stable URL without touching DNS.
FAQ
Why does my webhook work with ngrok but fail signature verification?
Usually something re-serialized the body before you hashed it. Verify against the raw bytes, not a parsed-and-restringified JSON object; tunnels forward bytes unchanged.
Can I use a Cloudflare quick tunnel for a permanent webhook endpoint?
No. The URL is random and changes on every run, so the provider's registered URL breaks as soon as you restart.
Do I need a tunnel if I use the provider's CLI?
Often not. Some providers ship a CLI that forwards events to localhost for you, which skips the public URL entirely, but it only covers that one provider.
Bottom line
Use Cloudflare's quick tunnel for a ten-minute smoke test. Use ngrok when you are debugging payloads and want replay. Use a named Cloudflare Tunnel if your domain is already on Cloudflare and you want a permanent URL you control. Use Tailscale Funnel if you already live on a tailnet. Whichever you choose, run the echo server first.
Top comments (0)