I built OpenTunnel (opens in a new tab) because I kept hitting the same friction: my local app was ready, the webhook provider was waiting, and my public URL had changed again.
If you ship anything that talks to the outside world - Stripe, GitHub, WhatsApp, OAuth callbacks, mobile clients - you eventually need localhost on the public internet. For a long time I lived with temporary tunnels. They work until they do not.
The problem with throwaway URLs
Most tunneling tools are great at the first five minutes. You expose port 3000, get a random HTTPS URL, paste it into a dashboard, and move on.
Then you restart the tunnel.
The URL changes. You update Stripe. You update GitHub. You update the mobile deep link you sent a teammate ten minutes ago. You lose the request that just failed because the free session already rotated. Debugging becomes a loop of reconfiguration instead of thinking about the bug.
I did not want a tunnel for demos once a week. I wanted a stable address for the work I do every day.
What I actually needed
The wishlist was short:
- Persistent domains - something like
my-app.opts.inkthat stays mine across restarts. - Automatic TLS - no fighting certificates when I just want to receive a webhook.
- Request inspection - see headers, body, status, and latency as traffic hits my machine.
- One-click replay - resend a failed delivery without poking the provider again.
- A single binary - install, authenticate, go live. Prefer open source and self-hostable.
That became OpenTunnel.
Webhooks were the forcing function
Webhooks are unforgiving about URLs. Providers store the endpoint you give them. If that endpoint is ephemeral, your local integration tax compounds:
- You cannot leave a Stripe test webhook pointed at the same place overnight.
- You cannot share one stable callback URL with a phone on the same Wi‑Fi story.
- You cannot reproduce yesterday’s failing payload if the tunnel that captured it is gone.
A persistent domain turns that into a boring setup: reserve a name once, point providers at it, and keep shipping. When something breaks, I open the inspector, read the request, replay it, and fix the handler - without leaving the tunnel.
That loop is why I use OpenTunnel every day. It is not a novelty for launch demos. It is part of how I develop.
Everyday use
On a normal day it looks like this:
Localhost is suddenly https://my-app.opts.ink. The same command works whether I am wiring payments, testing an OAuth redirect, showing a WIP UI on a phone, or letting a collaborator hit my machine without me deploying.
The dashboard matters as much as the URL. Live request streaming means I stop guessing what the provider sent. Instant replay means I stop asking Stripe to “send it again” while I stare at logs.
Why open source and self-hostable
I wanted something I would trust with real traffic and real secrets in request bodies. Open source keeps the tool honest. Self-hosting means you can run your own edge if you need the domain and data path fully under your control.
The hosted experience at opts.ink (opens in a new tab) is for speed: persistent *.opts.ink domains, TLS, inspection, replay. The same ideas should not lock you into one vendor’s session model.
What I care about next
OpenTunnel exists to make “localhost, publicly reachable” feel like infrastructure, not a temporary hack. Persistent domains were the reason I started. Daily webhook debugging is the reason I still open it before almost anything else.
If you have ever updated the same webhook URL three times before lunch, you already know the problem. Try OpenTunnel (opens in a new tab) - reserve a domain, point your providers at it once, and keep the URL.
