ngrok is great until you hit the free-tier walls: random URLs that expire when the tunnel restarts (breaking every webhook subscription you configured), session time limits, and an account requirement. If all you need is webhooks reaching your dev machine, you have options. Here's an honest rundown — including where each one beats the others.
$ cloudflared tunnel --url http://localhost:3000
Good: free, no account needed for quick tunnels, solid infrastructure.
Trade-offs: you get a random trycloudflare.com URL that changes
every run — so you're back to re-configuring the webhook provider each time. A stable named
tunnel requires an account and a domain. It's a full tunnel daemon, which some corporate
networks and CI environments block.
$ ssh -R 80:localhost:3000 nokey@localhost.run
Good: nothing to install (SSH is everywhere), no account.
Trade-offs: random URLs per session, variable reliability (these are mostly
volunteer/small services and go down or rate-limit), and some inject an interstitial page
that breaks non-browser webhook senders.
Good: built into tools you already run.
Trade-offs: URLs require auth by default (webhook senders can't log in),
tied to your editor session, not scriptable in CI.
All of the above solve "expose my laptop to the internet." But for webhooks you can flip the problem: give the provider a stable capture URL, and pull the captures down to localhost yourself.
$ curl -s https://catchhook.catchhook.workers.dev/cli -o catchhook && chmod +x catchhook $ curl https://catchhook.catchhook.workers.dev/new # → stable /h/… URL, paste into the provider once $ ./catchhook relay YOUR_BIN http://localhost:3000
Every webhook that arrives is re-delivered to your local server with the original method,
sub-path, query string, headers, and a byte-identical body — so HMAC
signatures (GitHub's X-Hub-Signature-256, Stripe's, Shopify's) still verify in
your real handler code.
Good:
/h/my-stripe-dev make it memorable).Trade-offs, honestly: it's polling, so delivery to localhost lags a couple of seconds behind the original webhook — fine for development, not for exposing a production service. It only speaks HTTP (no raw TCP/TLS tunneling, no custom domains pointing at your laptop). If you need a browser to reach your local app from the internet, you still want a real tunnel; if you need webhooks, this is simpler and stabler.
No signup needed. Or from your terminal: curl https://catchhook.catchhook.workers.dev/new