Skip to content

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

  1. Quick Start
  2. Installation
  3. Configuration
  4. Common Use Cases
  5. Troubleshooting
  6. Production Considerations
  7. Advanced Topics
  8. 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

Terminal window
# 1. Install ngrok
brew install ngrok/ngrok/ngrok # macOS
# 2. Get authtoken from https://dashboard.ngrok.com/get-started/your-authtoken
ngrok config add-authtoken YOUR_TOKEN_HERE
# 3. Start your local server
# Example: FastAPI
cd templates/python-saas
python src/ui/main.py # Runs on port 8000
# 4. In another terminal, start tunnel
ngrok http 8000
# 5. Copy the Forwarding URL (e.g., https://abc123.ngrok-free.app)
# Use this URL as your webhook endpoint

That’s it! Your local server is now accessible at the ngrok URL.


Installation

macOS

Terminal window
# Using Homebrew (Recommended)
brew install ngrok/ngrok/ngrok
# Verify installation
ngrok version

Linux

Terminal window
# Ubuntu/Debian
curl -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 installation
ngrok version

Windows

Terminal window
# Using Chocolatey
choco install ngrok
# Or download from https://ngrok.com/download

Manual Installation

  1. Download from https://ngrok.com/download

  2. Extract to /usr/local/bin/ (macOS/Linux) or C:\ngrok\ (Windows)

  3. Add to PATH:

    Terminal window
    # macOS/Linux
    export PATH="$PATH:/usr/local/bin"
    # Windows (PowerShell as Admin)
    [Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\ngrok", "Machine")

Configuration

Getting Your Authtoken

  1. Sign up at https://dashboard.ngrok.com/signup (free)
  2. Navigate to https://dashboard.ngrok.com/get-started/your-authtoken
  3. Copy your authtoken (format: 2abC123_DEfghI456JKlmNOp...)

Configure Authtoken

Terminal window
# One-time setup
ngrok config add-authtoken YOUR_TOKEN_HERE
# Verify configuration
cat ~/.config/ngrok/ngrok.yml # macOS/Linux
type %USERPROFILE%\AppData\Local\ngrok\ngrok.yml # Windows

Configuration 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 tunnels
tunnels:
python-saas:
proto: http
addr: 8000
react-client:
proto: http
addr: 5173
rails-api:
proto: http
addr: 3000

Advanced 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

Terminal window
cd foundry-meta/templates/python-saas
python src/ui/main.py # Starts FastAPI on port 8000

Step 2: Start ngrok Tunnel

Terminal window
ngrok http 8000

Output:

ngrok (Ctrl+C to quit)
Session Status online
Account YourName (Plan: Free)
Version 3.3.0
Region United States (us)
Latency 23ms
Web Interface http://127.0.0.1:4040
Forwarding https://abc123def456.ngrok-free.app -> http://localhost:8000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00

Step 3: Configure PactaPay Webhook

  1. Go to PactaPay Dashboard: https://dashboard.pactapay.com/webhooks
  2. Click “Add Endpoint”
  3. Webhook URL: https://abc123def456.ngrok-free.app/api/commerce/webhooks/pactapay
  4. Events to send: Select all or specific:
    • payment.completed
    • payment.failed
    • payment.refunded
  5. Click “Create Webhook”
  6. Copy the Webhook Secret (e.g., whsec_xyz789abc123)

Step 4: Configure Local Environment

Terminal window
# Add to .env file
echo "PACTAPAY_WEBHOOK_SECRET=whsec_xyz789abc123" >> .env

Step 5: Test Webhook

Option A: Use PactaPay Dashboard

  1. In PactaPay dashboard, find your webhook
  2. Click “Send test event”
  3. Select event type: payment.completed
  4. Click “Send”

Option B: Manual cURL Test

Terminal window
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:

Terminal window
open http://127.0.0.1:4040/inspect/http

Check Local Logs:

Terminal window
tail -f logs/app.log
# Should show: "POST /api/commerce/webhooks/pactapay - 200 OK"

Use Case 2: Shopify Order Webhook Testing

Terminal window
# 1. Start tunnel
ngrok http 8000
# 2. Configure Shopify App
# URL: https://YOUR_URL.ngrok-free.app/api/commerce/webhooks/shopify
# Topic: orders/create
# 3. Test with Shopify CLI
shopify webhook trigger --topic orders/create --api-version 2023-10
# 4. Monitor in ngrok UI
open http://127.0.0.1:4040

Use Case 3: GitHub Webhook Testing

Terminal window
# 1. Start tunnel for your webhook server
ngrok 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 push
git commit --allow-empty -m "Test webhook"
git push origin main
# 4. Check ngrok inspection UI
open http://127.0.0.1:4040

Use Case 4: Multiple Services (Docker Compose)

If running multiple services with Docker Compose:

docker-compose.yml
services:
api:
ports:
- "8000:8000"
client:
ports:
- "5173:5173"

Start multiple tunnels:

Terminal window
# Terminal 1: API tunnel
ngrok http 8000
# Terminal 2: Client tunnel
ngrok http 5173
# Or use ngrok config file:
ngrok start python-saas react-client

Troubleshooting

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:

Terminal window
# Check if installed
which ngrok
# If empty, install it
brew install ngrok/ngrok/ngrok # macOS
# If installed but not in PATH, add to PATH
export PATH="$PATH:/usr/local/bin"
# Make permanent (add to ~/.zshrc or ~/.bashrc)
echo 'export PATH="$PATH:/usr/local/bin"' >> ~/.zshrc
source ~/.zshrc
# Verify
ngrok version

Issue 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-authtoken

Cause: Authtoken not configured, expired, or incorrect.

Solution:

Terminal window
# Remove old config
rm ~/.config/ngrok/ngrok.yml
# Get new token from https://dashboard.ngrok.com/get-started/your-authtoken
# Add new token
ngrok config add-authtoken YOUR_NEW_TOKEN_HERE
# Verify
cat ~/.config/ngrok/ngrok.yml | grep authtoken
# Try again
ngrok http 8000

Issue 3: “ERR_NGROK_108: Connection refused”

Symptoms:

ERROR: failed to start tunnel: connect ECONNREFUSED 127.0.0.1:8000

Cause: Local server not running on the specified port.

Solution:

Terminal window
# Check if server is running on port 8000
lsof -i :8000
# If nothing, start your server first
cd templates/python-saas
python src/ui/main.py & # Runs in background
# Verify server is running
curl http://localhost:8000/health
# Then start tunnel
ngrok http 8000

Issue 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:

Terminal window
# Find and stop other ngrok processes
ps aux | grep ngrok
# Example output: 1234 ... ngrok http 3000
# Kill the process
kill 1234
# Or kill all ngrok processes
pkill ngrok
# Verify
ps aux | grep ngrok # Should return nothing
# Start your tunnel
ngrok http 8000

Alternative: 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:

Terminal window
# 1. Check ngrok is forwarding to correct port
# In ngrok output, verify: http://localhost:8000
# 2. Check local server is actually running
lsof -i :8000
# 3. Test endpoint locally
curl http://localhost:8000/api/commerce/webhooks/pactapay
# Should NOT return 404
# 4. Check route exists in your code
cd templates/python-saas
grep -r "webhooks/pactapay" src/

Solution:

  • Verify endpoint path matches your route configuration
  • Common mistake: /webhooks/pactapay vs /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:

Terminal window
# 1. Verify webhook secret is configured
cat .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 issues
echo "$PACTAPAY_WEBHOOK_SECRET" | od -c
# Should not show trailing spaces or newlines
# 4. Reload environment
source .env
python src/ui/main.py
# 5. Re-test webhook

Issue 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

~/.config/ngrok/ngrok.yml
tunnels:
dev:
proto: http
addr: 8000
Terminal window
# Start by name (still gets random URL but easier to reference)
ngrok start dev

Option 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

get_ngrok_url.sh
#!/bin/bash
curl -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:

Terminal window
# 1. Check ngrok region (use closest)
ngrok http 8000 --region us # or eu, ap, au, sa, jp, in
# 2. Monitor latency in ngrok UI
open http://127.0.0.1:4040
# 3. Verify local server response time
curl -w "@curl-format.txt" -o /dev/null -s http://localhost:8000/health
# curl-format.txt:
# time_total: %{time_total}s\n

Note: Free tier has a 40 requests/minute limit. Paid plans have higher limits.


Debugging Checklist

When webhooks fail, verify each step:

  1. ngrok tunnel running

    Terminal window
    ps aux | grep ngrok
  2. Local server running on correct port

    Terminal window
    lsof -i :8000
  3. Webhook URL configured in external service

    • Check dashboard: PactaPay, Shopify, GitHub, etc.
    • Verify URL is HTTPS (not HTTP)
  4. Endpoint path matches route

    Terminal window
    curl http://localhost:8000/api/commerce/webhooks/pactapay
  5. ngrok Web UI shows incoming request

    Terminal window
    open http://127.0.0.1:4040/inspect/http
  6. Local server logs show request received

    Terminal window
    tail -f logs/app.log
  7. Response status is 200 (not 404, 401, 500)

    • Check ngrok UI for response code
  8. 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

Terminal window
# AWS, GCP, Azure, DigitalOcean, Heroku, Railway, Render, etc.
# Your app gets a stable URL: https://your-app.vercel.app

Pros:

  • Stable URLs
  • SSL included
  • Scalable
  • Monitoring/logging

Cons:

  • Requires deployment pipeline
  • May have costs

Option 2: Self-Hosted with Domain + SSL

# nginx configuration
server {
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:

Terminal window
# Install certbot
sudo apt install certbot python3-certbot-nginx
# Get SSL certificate
sudo certbot --nginx -d api.yourdomain.com
# Auto-renewal
sudo certbot renew --dry-run

Option 3: Use Cloudflare Tunnel (Zero Trust)

Alternative to ngrok for production:

  • Free tier available
  • Stable URLs
  • DDoS protection
  • No exposed ports
Terminal window
# Install cloudflared
brew install cloudflare/cloudflare/cloudflared
# Authenticate
cloudflared tunnel login
# Create tunnel
cloudflared tunnel create my-app
# Route to local server
cloudflared tunnel route dns my-app api.yourdomain.com
# Start tunnel
cloudflared tunnel run --url http://localhost:8000 my-app

Advanced Topics

Using ngrok with Docker

Option 1: Run ngrok Outside Docker

Terminal window
# Start Docker services
docker-compose up -d
# Start ngrok on host
ngrok http 8000

Option 2: Run ngrok Inside Docker

docker-compose.yml
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 UI

Get tunnel URL:

Terminal window
curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url'

Multiple Simultaneous Tunnels

Using Config File:

~/.config/ngrok/ngrok.yml
version: "2"
authtoken: YOUR_TOKEN
tunnels:
api:
proto: http
addr: 8000
client:
proto: http
addr: 5173
admin:
proto: http
addr: 3001

Start all tunnels:

Terminal window
ngrok start api client admin

Note: Requires paid plan for multiple simultaneous tunnels.


Custom Domains (Paid Feature)

Setup:

  1. Configure DNS (add CNAME):

    api.yourdomain.com → YOUR_SUBDOMAIN.cname.ngrok.com
  2. Update ngrok config:

    tunnels:
    api:
    proto: http
    addr: 8000
    hostname: api.yourdomain.com
  3. 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

  1. Open http://127.0.0.1:4040/inspect/http
  2. Find the failed request (status 500)
  3. Inspect request headers and body
  4. Click “Replay” to trigger again
  5. Check local logs for errors

API Access to Tunnel Information

Get tunnel URL programmatically:

Terminal window
# ngrok exposes API on localhost:4040
curl -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:

auto_configure_webhook.sh
#!/bin/bash
# Get ngrok URL
NGROK_URL=$(curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url')
# Update webhook in service
curl -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

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

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


Need Help?

If you’re still having issues:

  1. Check ngrok status: https://status.ngrok.com
  2. Review ngrok logs: Look for error messages in terminal
  3. Test locally first: Ensure your endpoint works with curl http://localhost:8000/your/endpoint
  4. Check firewall: Ensure ports aren’t blocked
  5. 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)