Tunnel Configuration Guide
Tunnel Configuration Guide
Overview
The Tunnel feature uses ngrok to expose your local development server to the internet, enabling webhook testing from external services. This is essential for developing and testing integrations with payment processors, version control systems, and other third-party services that require callback URLs.
Use Cases:
- Testing PactaPay payment webhooks locally
- Receiving GitHub webhook events during development
- Testing Shopify order webhooks
- Testing Stripe payment notifications
- Testing external API callbacks
- Sharing local dev environment with team members
- Debugging production webhook issues locally
Who This Guide Is For:
- Developers setting up local webhook testing
- Teams debugging webhook integration issues
- Anyone needing to expose localhost to the internet temporarily
Table of Contents
- Quick Start
- Installation
- Configuration
- Common Use Cases
- Troubleshooting
- Production Considerations
- Advanced Topics
- Resources
Quick Start
Prerequisites
- Active internet connection
- Local server running (e.g., Python/FastAPI on port 8000, Rails on port 3000)
- ngrok account (free tier available)
5-Minute Setup
# 1. Install ngrokbrew install ngrok/ngrok/ngrok # macOS
# 2. Get authtoken from https://dashboard.ngrok.com/get-started/your-authtokenngrok config add-authtoken YOUR_TOKEN_HERE
# 3. Start your local server# Example: FastAPIcd templates/python-saaspython src/ui/main.py # Runs on port 8000
# 4. In another terminal, start tunnelngrok http 8000
# 5. Copy the Forwarding URL (e.g., https://abc123.ngrok-free.app)# Use this URL as your webhook endpointThat’s it! Your local server is now accessible at the ngrok URL.
Installation
macOS
# Using Homebrew (Recommended)brew install ngrok/ngrok/ngrok
# Verify installationngrok versionLinux
# Ubuntu/Debiancurl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | \ sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && \ echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | \ sudo tee /etc/apt/sources.list.d/ngrok.list && \ sudo apt update && sudo apt install ngrok
# Verify installationngrok versionWindows
# Using Chocolateychoco install ngrok
# Or download from https://ngrok.com/downloadManual Installation
-
Download from https://ngrok.com/download
-
Extract to
/usr/local/bin/(macOS/Linux) orC:\ngrok\(Windows) -
Add to PATH:
Terminal window # macOS/Linuxexport PATH="$PATH:/usr/local/bin"# Windows (PowerShell as Admin)[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\ngrok", "Machine")
Configuration
Getting Your Authtoken
- Sign up at https://dashboard.ngrok.com/signup (free)
- Navigate to https://dashboard.ngrok.com/get-started/your-authtoken
- Copy your authtoken (format:
2abC123_DEfghI456JKlmNOp...)
Configure Authtoken
# One-time setupngrok config add-authtoken YOUR_TOKEN_HERE
# Verify configurationcat ~/.config/ngrok/ngrok.yml # macOS/Linuxtype %USERPROFILE%\AppData\Local\ngrok\ngrok.yml # WindowsConfiguration File Location
- macOS/Linux:
~/.config/ngrok/ngrok.yml - Windows:
%USERPROFILE%\AppData\Local\ngrok\ngrok.yml
Basic Configuration
version: "2"authtoken: YOUR_TOKEN_HERE
# Optional: Define named tunnelstunnels: python-saas: proto: http addr: 8000
react-client: proto: http addr: 5173
rails-api: proto: http addr: 3000Advanced Configuration
version: "2"authtoken: YOUR_TOKEN_HERE
tunnels: python-saas: proto: http addr: 8000 inspect: true # Enable web UI inspection bind_tls: true # HTTPS only (recommended for webhooks) host_header: rewrite # Fix host header issues
# Custom subdomain (requires paid plan) subdomain: my-api-dev
# Custom domain (requires paid plan + DNS setup) hostname: api-dev.yourdomain.com
# IP restrictions (requires paid plan) ip_restriction: allow_cidrs: - 203.0.113.0/24 # Your office IP range
# Basic auth (requires paid plan) basic_auth: - "dev:secretpassword"Common Use Cases
Use Case 1: PactaPay Webhook Testing
Scenario: You’re developing a payment integration and need to test webhook callbacks.
Step 1: Start Your Local Server
cd foundry-meta/templates/python-saaspython src/ui/main.py # Starts FastAPI on port 8000Step 2: Start ngrok Tunnel
ngrok http 8000Output:
ngrok (Ctrl+C to quit)
Session Status onlineAccount YourName (Plan: Free)Version 3.3.0Region United States (us)Latency 23msWeb Interface http://127.0.0.1:4040Forwarding https://abc123def456.ngrok-free.app -> http://localhost:8000
Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00Step 3: Configure PactaPay Webhook
- Go to PactaPay Dashboard: https://dashboard.pactapay.com/webhooks
- Click “Add Endpoint”
- Webhook URL:
https://abc123def456.ngrok-free.app/api/commerce/webhooks/pactapay - Events to send: Select all or specific:
payment.completedpayment.failedpayment.refunded
- Click “Create Webhook”
- Copy the Webhook Secret (e.g.,
whsec_xyz789abc123)
Step 4: Configure Local Environment
# Add to .env fileecho "PACTAPAY_WEBHOOK_SECRET=whsec_xyz789abc123" >> .envStep 5: Test Webhook
Option A: Use PactaPay Dashboard
- In PactaPay dashboard, find your webhook
- Click “Send test event”
- Select event type:
payment.completed - Click “Send”
Option B: Manual cURL Test
curl -X POST https://abc123def456.ngrok-free.app/api/commerce/webhooks/pactapay \ -H "Content-Type: application/json" \ -H "Pactapay-Signature: t=1234567890,v1=abc123..." \ -d '{ "id": "evt_test_123", "type": "payment.completed", "data": { "payment_id": "pay_test_456", "amount": 5000, "currency": "usd" } }'Step 6: Verify Reception
Check ngrok Web UI:
open http://127.0.0.1:4040/inspect/httpCheck Local Logs:
tail -f logs/app.log# Should show: "POST /api/commerce/webhooks/pactapay - 200 OK"Use Case 2: Shopify Order Webhook Testing
# 1. Start tunnelngrok http 8000
# 2. Configure Shopify App# URL: https://YOUR_URL.ngrok-free.app/api/commerce/webhooks/shopify# Topic: orders/create
# 3. Test with Shopify CLIshopify webhook trigger --topic orders/create --api-version 2023-10
# 4. Monitor in ngrok UIopen http://127.0.0.1:4040Use Case 3: GitHub Webhook Testing
# 1. Start tunnel for your webhook serverngrok http 8000
# 2. Configure GitHub Repository# Settings → Webhooks → Add webhook# Payload URL: https://YOUR_URL.ngrok-free.app/webhooks/github# Content type: application/json# Events: Push events, Pull requests
# 3. Make a test pushgit commit --allow-empty -m "Test webhook"git push origin main
# 4. Check ngrok inspection UIopen http://127.0.0.1:4040Use Case 4: Multiple Services (Docker Compose)
If running multiple services with Docker Compose:
services: api: ports: - "8000:8000"
client: ports: - "5173:5173"Start multiple tunnels:
# Terminal 1: API tunnelngrok http 8000
# Terminal 2: Client tunnelngrok http 5173
# Or use ngrok config file:ngrok start python-saas react-clientTroubleshooting
Issue 1: “ngrok: command not found”
Symptoms: Terminal returns “command not found” when running ngrok.
Cause: ngrok not installed or not in system PATH.
Solution:
# Check if installedwhich ngrok
# If empty, install itbrew install ngrok/ngrok/ngrok # macOS
# If installed but not in PATH, add to PATHexport PATH="$PATH:/usr/local/bin"
# Make permanent (add to ~/.zshrc or ~/.bashrc)echo 'export PATH="$PATH:/usr/local/bin"' >> ~/.zshrcsource ~/.zshrc
# Verifyngrok versionIssue 2: “ERR_NGROK_105: Invalid authtoken”
Symptoms:
ERROR: authentication failed: Your authtoken is not valid. Retrieve your authtoken by visiting https://dashboard.ngrok.com/get-started/your-authtokenCause: Authtoken not configured, expired, or incorrect.
Solution:
# Remove old configrm ~/.config/ngrok/ngrok.yml
# Get new token from https://dashboard.ngrok.com/get-started/your-authtoken# Add new tokenngrok config add-authtoken YOUR_NEW_TOKEN_HERE
# Verifycat ~/.config/ngrok/ngrok.yml | grep authtoken
# Try againngrok http 8000Issue 3: “ERR_NGROK_108: Connection refused”
Symptoms:
ERROR: failed to start tunnel: connect ECONNREFUSED 127.0.0.1:8000Cause: Local server not running on the specified port.
Solution:
# Check if server is running on port 8000lsof -i :8000
# If nothing, start your server firstcd templates/python-saaspython src/ui/main.py & # Runs in background
# Verify server is runningcurl http://localhost:8000/health
# Then start tunnelngrok http 8000Issue 4: “ERR_NGROK_326: Account limit reached”
Symptoms:
ERROR: Your account is limited to 1 simultaneous ngrok agent session.Cause: Free plan allows only 1 tunnel at a time. Another ngrok process is running.
Solution:
# Find and stop other ngrok processesps aux | grep ngrok# Example output: 1234 ... ngrok http 3000
# Kill the processkill 1234
# Or kill all ngrok processespkill ngrok
# Verifyps aux | grep ngrok # Should return nothing
# Start your tunnelngrok http 8000Alternative: Upgrade to paid plan for multiple tunnels ($8/month).
Issue 5: Webhook Reaches ngrok But Returns 404
Symptoms:
- ngrok Web UI shows incoming request
- Response: 404 Not Found
- Local logs show nothing
Diagnosis:
# 1. Check ngrok is forwarding to correct port# In ngrok output, verify: http://localhost:8000
# 2. Check local server is actually runninglsof -i :8000
# 3. Test endpoint locallycurl http://localhost:8000/api/commerce/webhooks/pactapay# Should NOT return 404
# 4. Check route exists in your codecd templates/python-saasgrep -r "webhooks/pactapay" src/Solution:
- Verify endpoint path matches your route configuration
- Common mistake:
/webhooks/pactapayvs/api/commerce/webhooks/pactapay
Issue 6: Webhook Returns 401 Unauthorized
Symptoms:
- Request reaches your server
- Returns 401 Unauthorized
- Logs show: “Invalid webhook signature”
Cause: Webhook signature verification failing.
Solution:
# 1. Verify webhook secret is configuredcat .env | grep WEBHOOK_SECRET
# 2. Ensure secret matches dashboard# PactaPay: https://dashboard.pactapay.com/webhooks → Show Secret# Should match .env value exactly
# 3. Check for whitespace issuesecho "$PACTAPAY_WEBHOOK_SECRET" | od -c# Should not show trailing spaces or newlines
# 4. Reload environmentsource .envpython src/ui/main.py
# 5. Re-test webhookIssue 7: Tunnel URL Changes on Every Restart
Symptoms: ngrok URL like https://abc123.ngrok-free.app changes every time you restart ngrok.
Cause: Free plan assigns random URLs.
Solution Options:
Option A: Use Named Tunnels
tunnels: dev: proto: http addr: 8000# Start by name (still gets random URL but easier to reference)ngrok start devOption B: Upgrade to Paid Plan
- Static subdomain:
https://your-name.ngrok-free.app - Custom domain:
https://api.yourdomain.com - Pricing: https://ngrok.com/pricing ($8/month Personal plan)
Option C: Use Script to Auto-Update Webhook
#!/bin/bashcurl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url'Issue 8: Requests Are Slow Through ngrok
Symptoms: Requests take 2-5 seconds vs <100ms locally.
Cause: Free tier has rate limiting and adds latency.
Solutions:
# 1. Check ngrok region (use closest)ngrok http 8000 --region us # or eu, ap, au, sa, jp, in
# 2. Monitor latency in ngrok UIopen http://127.0.0.1:4040
# 3. Verify local server response timecurl -w "@curl-format.txt" -o /dev/null -s http://localhost:8000/health
# curl-format.txt:# time_total: %{time_total}s\nNote: Free tier has a 40 requests/minute limit. Paid plans have higher limits.
Debugging Checklist
When webhooks fail, verify each step:
-
✅ ngrok tunnel running
Terminal window ps aux | grep ngrok -
✅ Local server running on correct port
Terminal window lsof -i :8000 -
✅ Webhook URL configured in external service
- Check dashboard: PactaPay, Shopify, GitHub, etc.
- Verify URL is HTTPS (not HTTP)
-
✅ Endpoint path matches route
Terminal window curl http://localhost:8000/api/commerce/webhooks/pactapay -
✅ ngrok Web UI shows incoming request
Terminal window open http://127.0.0.1:4040/inspect/http -
✅ Local server logs show request received
Terminal window tail -f logs/app.log -
✅ Response status is 200 (not 404, 401, 500)
- Check ngrok UI for response code
-
✅ Signature validation passes (if applicable)
Terminal window cat .env | grep WEBHOOK_SECRET
Production Considerations
⚠️ DO NOT Use ngrok in Production
Why ngrok is for development only:
- URLs change on every restart (free plan)
- No uptime SLA or guarantees
- Rate limiting on free tier (40 req/min)
- Not designed for production traffic
- Single point of failure
- Limited to 1 tunnel on free plan
Production Alternatives
Option 1: Deploy to Cloud Provider
# AWS, GCP, Azure, DigitalOcean, Heroku, Railway, Render, etc.# Your app gets a stable URL: https://your-app.vercel.appPros:
- Stable URLs
- SSL included
- Scalable
- Monitoring/logging
Cons:
- Requires deployment pipeline
- May have costs
Option 2: Self-Hosted with Domain + SSL
# nginx configurationserver { listen 443 ssl http2; server_name api.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
location / { proxy_pass http://localhost:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }}Setup:
# Install certbotsudo apt install certbot python3-certbot-nginx
# Get SSL certificatesudo certbot --nginx -d api.yourdomain.com
# Auto-renewalsudo certbot renew --dry-runOption 3: Use Cloudflare Tunnel (Zero Trust)
Alternative to ngrok for production:
- Free tier available
- Stable URLs
- DDoS protection
- No exposed ports
# Install cloudflaredbrew install cloudflare/cloudflare/cloudflared
# Authenticatecloudflared tunnel login
# Create tunnelcloudflared tunnel create my-app
# Route to local servercloudflared tunnel route dns my-app api.yourdomain.com
# Start tunnelcloudflared tunnel run --url http://localhost:8000 my-appAdvanced Topics
Using ngrok with Docker
Option 1: Run ngrok Outside Docker
# Start Docker servicesdocker-compose up -d
# Start ngrok on hostngrok http 8000Option 2: Run ngrok Inside Docker
services: api: build: ./templates/python-saas ports: - "8000:8000"
ngrok: image: ngrok/ngrok:alpine command: http api:8000 environment: - NGROK_AUTHTOKEN=${NGROK_AUTHTOKEN} ports: - "4040:4040" # Inspect UIGet tunnel URL:
curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url'Multiple Simultaneous Tunnels
Using Config File:
version: "2"authtoken: YOUR_TOKEN
tunnels: api: proto: http addr: 8000
client: proto: http addr: 5173
admin: proto: http addr: 3001Start all tunnels:
ngrok start api client adminNote: Requires paid plan for multiple simultaneous tunnels.
Custom Domains (Paid Feature)
Setup:
-
Configure DNS (add CNAME):
api.yourdomain.com → YOUR_SUBDOMAIN.cname.ngrok.com -
Update ngrok config:
tunnels:api:proto: httpaddr: 8000hostname: api.yourdomain.com -
Start tunnel:
Terminal window ngrok start api
Result: Always accessible at https://api.yourdomain.com
Inspecting and Replaying Requests
ngrok Web Interface: http://127.0.0.1:4040
Features:
- View all requests: See headers, body, timing
- Replay requests: Click “Replay” to resend
- Edit and replay: Modify headers/body before resending
- Filter requests: By path, status code, method
Example: Debug Failed Webhook
- Open http://127.0.0.1:4040/inspect/http
- Find the failed request (status 500)
- Inspect request headers and body
- Click “Replay” to trigger again
- Check local logs for errors
API Access to Tunnel Information
Get tunnel URL programmatically:
# ngrok exposes API on localhost:4040curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url'Response:
{ "tunnels": [ { "name": "command_line", "uri": "/api/tunnels/command_line", "public_url": "https://abc123.ngrok-free.app", "proto": "https", "config": { "addr": "http://localhost:8000", "inspect": true }, "metrics": { "conns": { "count": 0, "gauge": 0, "rate1": 0, "rate5": 0, "rate15": 0, "p50": 0, "p90": 0, "p95": 0, "p99": 0 }, "http": { "count": 0, "rate1": 0, "rate5": 0, "rate15": 0, "p50": 0, "p90": 0, "p95": 0, "p99": 0 } } } ]}Use in script:
#!/bin/bash# Get ngrok URLNGROK_URL=$(curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url')
# Update webhook in servicecurl -X POST https://api.pactapay.com/webhooks \ -H "Authorization: Bearer $PACTAPAY_API_KEY" \ -d "url=${NGROK_URL}/api/commerce/webhooks/pactapay"
echo "Webhook configured: ${NGROK_URL}/api/commerce/webhooks/pactapay"Resources
Official Documentation
- ngrok Docs: https://ngrok.com/docs
- ngrok API: https://ngrok.com/docs/api
- ngrok GitHub: https://github.com/ngrok/ngrok
Pricing
- Free Tier: 1 tunnel, 40 req/min, random URLs
- Personal ($8/mo): 3 tunnels, custom subdomains, IP restrictions
- Pro ($20/mo): 10 tunnels, custom domains, reserved domains
- Full Pricing: https://ngrok.com/pricing
Service Status
- Status Page: https://status.ngrok.com
- Uptime Monitoring: Subscribe for notifications
Alternatives to ngrok
- Cloudflare Tunnel: Free, production-ready
- localtunnel: Open-source, free
- Tailscale Funnel: Free for personal use
- serveo: SSH-based, no client install
- expose: Laravel developers
Community
- ngrok Community: https://ngrok.com/community
- Stack Overflow: https://stackoverflow.com/questions/tagged/ngrok
- Reddit: r/ngrok
Related Documentation
Need Help?
If you’re still having issues:
- Check ngrok status: https://status.ngrok.com
- Review ngrok logs: Look for error messages in terminal
- Test locally first: Ensure your endpoint works with
curl http://localhost:8000/your/endpoint - Check firewall: Ensure ports aren’t blocked
- Review webhook provider docs: Each service has specific requirements
For Seed & Source specific issues, see:
Last Updated: February 28, 2026 Maintainer: Seed & Source Team Related Tasks: Task 066, Task 069 (Webhook Troubleshooting)