Stopping Webhook Breakage: Using Ngrok Reserved Domains for Stable Endpoints
Stop updating webhook URLs every time you restart your dev server. Learn how to use ngrok reserved domains to create stable, persistent inbound endpoints for local development.
09 Dec 2025, 15:14 UTC

The Ephemeral URL Problem
When developing a service that relies on webhooks—such as a Stripe payment listener, a GitHub Action trigger, or a Slack bot—you need a public URL for the provider to send data to. For local development, ngrok is the standard tool for creating this tunnel. However, the default behavior of the free tier is ephemeral: every time you restart the ngrok agent, you get a new, random .ngrok-free.app URL.
This creates a manual bottleneck. Every time your machine reboots or the agent crashes, you must copy the new URL and update the webhook settings in the third-party provider's dashboard. This is not only tedious but makes automated testing and team collaboration nearly impossible. The solution is using Reserved Domains, which decouple your public endpoint from the lifecycle of the local agent process.
How Reserved Domains Stabilize the Tunnel
A reserved domain is a static address (either a custom domain you own or a specific ngrok subdomain) that is permanently mapped to your ngrok account. Instead of the ngrok edge server assigning a random ID to your tunnel, it looks for the specific domain requested in your configuration and routes that traffic to whichever agent is currently authenticated and claiming that domain.
This setup provides two critical benefits for engineering workflows:
- Persistence: Your webhook provider's callback URL never changes, regardless of how many times you restart your local server.
- Professionalism: If using a custom domain (e.g.,
api-dev.example.com), you avoid the "free-tier" branding and can maintain a consistent naming convention across different development environments.
Implementation: From DNS to Localhost
Setting up a reserved domain requires a combination of dashboard configuration and a local configuration file. This process assumes you are using a paid ngrok plan, as reserved domains are not available on the free tier.
1. Domain Verification
In the ngrok dashboard, you must first add the domain. If you are using a custom domain you own, ngrok will provide a CNAME record. You must add this record to your DNS provider (e.g., Cloudflare, Route53) to delegate traffic to ngrok's edge.
2. Local Configuration
Rather than using a one-off command line flag, the most reliable way to manage reserved domains is via the ngrok.yml configuration file. This ensures that the agent always requests the correct domain upon startup.
# Example ngrok.yml configuration
version: "2"
authtoken: YOUR_AUTH_TOKEN
tunnels:
webhook-receiver:
proto: http
addr: 8080
domain: dev-webhook.example.com
3. Launching the Tunnel
Run the agent from your terminal, referencing the tunnel name defined in the YAML file. You must run this command with the permissions of the user who owns the ngrok.yml file.
# Run this on your local development machine
ngrok start webhook-receiver
Expected Check: The terminal output should explicitly show the status as online and list the domain: dev-webhook.example.com. If the domain is misconfigured in the DNS or dashboard, the agent will fail to start with a domain_not_reserved or dns_configuration_error.
Trade-offs and Security Constraints
While reserved domains solve the stability problem, they introduce a new security consideration: permanent exposure. Because the URL is static, it is more susceptible to discovery by bots or unauthorized users than a random ephemeral URL.
Furthermore, it is important to understand where encryption happens. Ngrok terminates TLS (HTTPS) at its edge. The traffic from the ngrok edge to your local machine is typically unencrypted HTTP. If your application requires end-to-end encryption (TLS all the way to localhost), you must configure local certificates and use the https protocol in the addr field of your config.
Verifying the Setup
To confirm the stability of your endpoint, perform the following sequence:
- Start the tunnel and send a test payload using
curlor a tool like Postman to your reserved domain. Confirm your local app receives the request. - Kill the ngrok process (Ctrl+C).
- Restart the agent using
ngrok start webhook-receiver. - Send the same test payload again. If the request succeeds without you changing the URL, the reservation is active and stable.
Rollback: To stop exposing your local port, simply terminate the ngrok process. To remove the domain mapping entirely, delete the domain from the ngrok dashboard and remove the CNAME record from your DNS provider.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.