ToolingSeptember 13, 20265 min read

Your Tunnel URL Changes Every Restart. Your Webhook URL Doesn't Have To.

ngrok hands you a new URL every restart, so you go and re-paste it into Stripe or Shopify. Put one permanent URL at the provider and point it at whatever tunnel is alive today.

TUNNEL · THE URL IS BOUND TO A LAPTOPStripetunnelrotates, expireslaptopasleepevent goneRELAY · THE URL IS BOUND TO NOTHINGStriperelaypersist + 200prodlaptopreplayable later
The provider should be talking to an address that outlives your terminal.

You run ngrok http 3000. You copy the URL. You paste it into the Stripe dashboard. You debug for an hour, close the laptop, and tomorrow morning the whole ritual runs again because the URL is gone.

This is not a hard problem. It is a papercut that reopens every single day, and most of the answers to it are "pay for a static domain" or "use a different tunnel". There is a third option that costs nothing and fixes it at a different layer.

Register an address that outlives the terminal

The provider does not need to know where your laptop is. It needs one URL that is always there.

Stripe / Shopify / GitHub
        │
        ▼
in.anyhook.net/you/dev      ← registered once, never changes
        │
        ▼
https://whatever-ngrok-gave-you-today.ngrok-free.app/webhook

The provider talks to the permanent half. You change the temporary half whenever your tunnel restarts, in one field, without touching the provider's dashboard. The change reaches every edge location within about a minute.

That is the whole idea. The rest of this post is why it is worth the five minutes, and where it does not help.

Shopify makes this worse than a papercut

On most providers a stale URL means failed deliveries you can ignore. On Shopify it means losing the subscription.

The Shopify CLI registers webhook endpoints at deploy time. Start your dev server, get a new tunnel URL, and the registration still points at yesterday's address. Shopify delivers to it, fails, retries, and after enough consecutive failures deletes the subscription entirely. Now nothing arrives even after you fix the tunnel, and the failure looks like "Shopify stopped sending" rather than "I changed my URL".

WooCommerce disables a webhook after five consecutive failures. Stripe disables an endpoint after continued failure. A URL that changes daily is a machine for producing consecutive failures.

What this does that a paid static domain does not

A static tunnel domain solves the re-pasting. It does not solve the part where your laptop is closed.

With a relay in front, events that arrive while your tunnel is down are received, verified, and written to a log before any delivery is attempted. Delivery to your machine fails, which is expected and fine. The event is still there.

  • Come back, start the tunnel, hit replay. The events from while you were away run against your handler in order
  • Replays do not count against your monthly quota
  • You can see exactly what the provider sent, headers and body, instead of inferring it from a handler that never ran

The free tier is one app, 3,000 events a month and 100 a day, which is more than a development loop uses. Events stay replayable for three days on that tier, which is the honest limit: this is a safety net for the weekend, not an archive.

Where it does not help

  • It does not keep your handler running. While the tunnel is down, deliveries to it fail. What you get is the log and the replay button, not delivery to a machine that is switched off
  • It is not a reason to skip signature verification. The provider's original headers are forwarded intact, so the signature you were checking is still the signature you check
  • For production, the tunnel should be gone entirely. That is a different argument, and we made it in Why ngrok Isn't Enough. ngrok remains the right tool for the dev loop; this post is only about the URL churn it creates

Setting it up

  1. Create an app. You get in.anyhook.net/{you}/{app} immediately
  2. Put that URL in the provider's dashboard once
  3. Set the destination to your current tunnel URL
  4. Send a test event from the app page and watch it land

Tomorrow, when the tunnel hands you a new address, you change step 3 and nothing else. If you later rename the app or your account, the old URL keeps routing rather than 404ing, because a URL you have already handed to a provider should never stop working.

Takeaway

The provider's dashboard is the wrong place to store an address that changes every day. Put something permanent there, keep the temporary part on your side, and the daily ritual goes away along with the failed-delivery counters it was quietly feeding.

If you want the deeper version of this, the one about signature verification surviving retries and what to do when a provider has already disabled you, start with Verifying Webhook Signatures and How to Replay a Webhook.

All postsSeptember 13, 2026 · 5 min

Stop losing webhooks.

Change one URL. Get retries, event log, and one-click replay.