Skip to main content

Best Practices & Troubleshooting

Best practices for using ngrok with Django-CFG and solutions to common issues.

Best Practices

1. Development Only

Ngrok should only be used in development, never in production.

# ✅ CORRECT - ngrok only in development
from django_cfg import DjangoConfig, NgrokConfig

class MyConfig(DjangoConfig):
ngrok: NgrokConfig = NgrokConfig(
enabled=True # Automatically disabled if DEBUG=False
)

# ❌ WRONG - enable ngrok in production
# Ngrok is NOT for production! Only for local development.

Even better - explicit environment check:

from .environment import env

class MyConfig(DjangoConfig):
# Ngrok only in development
ngrok: NgrokConfig = NgrokConfig(
enabled=(env.environment == "development")
)

2. Use Helper Functions

Always use Django-CFG helper functions instead of manual URL construction.

# ✅ CORRECT - automatic fallback
from django_cfg.modules.django_ngrok import get_webhook_url

webhook_url = get_webhook_url("/webhooks/")
# If ngrok active: "https://abc123.ngrok.io/webhooks/"
# If not: "http://localhost:8000/webhooks/" (or api_url from config)

# ❌ WRONG - hardcode URL
webhook_url = "http://localhost:8000/webhooks/" # Won't work with ngrok!

# ❌ WRONG - manual construction
import os
ngrok_url = os.environ.get('NGROK_URL')
webhook_url = f"{ngrok_url}/webhooks/" if ngrok_url else "http://localhost:8000/webhooks/"

3. Check Tunnel Status Before Critical Operations

# ✅ CORRECT - check before using
from django_cfg.modules.django_ngrok import is_tunnel_active, get_webhook_url

if is_tunnel_active():
print("✅ Using ngrok tunnel for webhooks")
else:
print("⚠️ Ngrok not active, using fallback URL")

webhook_url = get_webhook_url("/webhooks/")

# ❌ WRONG - assume tunnel is always active
webhook_url = get_webhook_url("/webhooks/") # May return fallback URL!

4. Log Webhook Events

Always log webhook events for debugging.

# ✅ CORRECT - log webhooks for debugging
import logging

logger = logging.getLogger(__name__)

def webhook_handler(request):
logger.info(f"Webhook received from {request.META.get('HTTP_HOST')}")
logger.debug(f"Headers: {dict(request.headers)}")
logger.debug(f"Payload: {request.body.decode()}")

# Process webhook
# ...

return JsonResponse({"status": "ok"})

5. Environment-Specific Configuration

Use different configurations for different environments.

# ✅ CORRECT - different settings for dev/prod
from .environment import env

class MyConfig(DjangoConfig):
# Ngrok only in development
ngrok: NgrokConfig = NgrokConfig(
enabled=(env.environment == "development"),
webhook_path="/api/webhooks/"
)

# Production uses real domain
api_url: str = (
"https://api.myapp.com"
if env.environment == "production"
else "http://localhost:8000"
)

6. Use Type Hints

Always use type hints for better IDE support.

# ✅ CORRECT - type hints
from typing import Optional
from django_cfg.modules.django_ngrok import get_tunnel_url

def get_public_url() -> Optional[str]:
"""Get public URL for webhooks."""
return get_tunnel_url()

# ✅ CORRECT - type hints for service
class WebhookService:
def __init__(self):
self.base_url: str = get_tunnel_url() or "http://localhost:8000"

def get_webhook_url(self, path: str) -> str:
return f"{self.base_url}{path}"

Common Issues

Issue 1: Tunnel Not Starting

Symptom:

$ python manage.py runserver_ngrok
❌ Error: No module named 'ngrok'

Solution:

# Check that ngrok is available
python -c "import ngrok; print('OK')"

# If error "No module named 'ngrok'":
pip install ngrok

# For Python < 3.12 may need:
pip install pyngrok

Verify installation:

# Check ngrok is installed
import ngrok
print(f"Ngrok version: {ngrok.__version__}")

Issue 2: Webhooks Not Received

Symptom:

External service sends webhook but Django doesn't receive it

Solution 1: Check tunnel is active

from django_cfg.modules.django_ngrok import is_tunnel_active, get_tunnel_url

print(f"Tunnel active: {is_tunnel_active()}")
print(f"Tunnel URL: {get_tunnel_url()}")

# If not active:
# - Restart with: python manage.py runserver_ngrok
# - Verify ngrok: NgrokConfig(enabled=True)

Solution 2: Check ALLOWED_HOSTS

from django.conf import settings

print(f"ALLOWED_HOSTS: {settings.ALLOWED_HOSTS}")

# Should include ngrok host like:
# ['localhost', '127.0.0.1', 'abc123.ngrok.io']

# If not:
# - Restart server with runserver_ngrok
# - Check NgrokConfig(enabled=True)

Solution 3: Verify webhook URL

from django_cfg.modules.django_ngrok import get_webhook_url

webhook_url = get_webhook_url('/api/webhooks/')
print(f"Webhook URL: {webhook_url}")

# Should be like: https://abc123.ngrok.io/api/webhooks/
# NOT: http://localhost:8000/api/webhooks/

Solution 4: Check URL routing

# urls.py
from django.urls import path
from . import views

urlpatterns = [
# Ensure webhook path exists
path('api/webhooks/stripe/', views.stripe_webhook, name='stripe_webhook'),
]

# Test URL manually:
# curl https://abc123.ngrok.io/api/webhooks/stripe/

Issue 3: Auth Token Issues

Symptom:

Error: Invalid ngrok auth token

Solution:

# If need advanced features (custom domain, etc):
export NGROK_AUTHTOKEN="your-ngrok-token"

# Get token from: https://dashboard.ngrok.com/get-started/your-authtoken

Or configure in code:

from django_cfg import NgrokConfig, NgrokAuthConfig

ngrok: NgrokConfig = NgrokConfig(
enabled=True,
auth=NgrokAuthConfig(
authtoken_from_env=True # Load from NGROK_AUTHTOKEN
)
)

Issue 4: CSRF Token Errors

Symptom:

403 Forbidden - CSRF verification failed

Solution:

# Disable CSRF for webhook endpoints
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def stripe_webhook(request):
# Process webhook
return JsonResponse({"status": "ok"})

Or add ngrok host to trusted origins:

# settings.py (generated by Django-CFG)
CSRF_TRUSTED_ORIGINS = [
"https://*.ngrok.io",
"https://*.ngrok-free.app",
]

Issue 5: Tunnel URL Changes

Symptom:

Ngrok URL changes on each restart (free plan)

Solutions:

Option 1: Use helper functions (recommended)

# ✅ Always use helper functions
from django_cfg.modules.django_ngrok import get_webhook_url

# URL automatically updated on each restart
webhook_url = get_webhook_url("/webhooks/")

Option 2: Custom domain (paid plan)

from django_cfg import NgrokConfig, NgrokTunnelConfig, NgrokAuthConfig

ngrok: NgrokConfig = NgrokConfig(
enabled=True,
auth=NgrokAuthConfig(
authtoken_from_env=True # Requires token
),
tunnel=NgrokTunnelConfig(
domain="myapp.ngrok.io" # Fixed domain (paid plan)
)
)

Issue 6: Slow Webhook Response

Symptom:

Webhooks timeout or are very slow

Solutions:

Solution 1: Enable compression

from django_cfg import NgrokConfig, NgrokTunnelConfig

ngrok: NgrokConfig = NgrokConfig(
enabled=True,
tunnel=NgrokTunnelConfig(
compression=True # Enable gzip compression
)
)

Solution 2: Use async webhook processing

# Process webhooks asynchronously
import django_rq
from django.http import JsonResponse

@csrf_exempt
def stripe_webhook(request):
# Immediately return success
# Process webhook in background

payload = request.body
process_stripe_webhook.delay(payload.decode())

return JsonResponse({"status": "received"})

@task
async def process_stripe_webhook(payload: str):
# Process webhook in background
# ...
pass

Issue 7: Environment Variables Not Set

Symptom:

print(os.environ.get('NGROK_URL'))  # None

Solution:

# Make sure you use runserver_ngrok
python manage.py runserver_ngrok # NOT runserver!

# Verify variables are set:
echo $NGROK_URL
echo $NGROK_HOST
echo $NGROK_SCHEME

In Python:

import os

# Check environment variables
ngrok_url = os.environ.get('NGROK_URL')
if not ngrok_url:
print("⚠️ NGROK_URL not set!")
print("Did you use 'runserver_ngrok' instead of 'runserver'?")

Issue 8: Multiple Tunnels

Symptom:

Multiple ngrok tunnels running at same time

Solution:

# Stop all ngrok processes
pkill -f ngrok

# Restart server
python manage.py runserver_ngrok

Or programmatically:

from django_cfg.modules.django_ngrok import get_ngrok_service

# Stop existing tunnel
service = get_ngrok_service()
service.stop_tunnel()

# Start new tunnel
service.start_tunnel(port=8000)

Debugging Tips

1. Check Ngrok Web Interface

Ngrok provides a web interface for debugging:

# Start server
python manage.py runserver_ngrok

# Open ngrok web interface
# http://127.0.0.1:4040

# Shows:
# - All HTTP requests
# - Request/response details
# - Timing information

2. Enable Verbose Logging

# config.py
import logging

# Enable debug logging
logging.basicConfig(level=logging.DEBUG)

# Or in Django settings:
LOGGING = {
'version': 1,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django_cfg.modules.django_ngrok': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}

3. Test Webhook Manually

# Test webhook with curl
curl -X POST https://abc123.ngrok.io/api/webhooks/test/ \
-H "Content-Type: application/json" \
-d '{"event": "test", "data": {"amount": 1000}}'

# Should return webhook response

4. Monitor Django Logs

# In webhook handler
import logging

logger = logging.getLogger(__name__)

def webhook_handler(request):
logger.info(f"📥 Webhook received")
logger.debug(f"Headers: {dict(request.headers)}")
logger.debug(f"Body: {request.body.decode()}")

# Process webhook
result = process_webhook(request)

logger.info(f"✅ Webhook processed: {result}")
return JsonResponse({"status": "ok"})

Performance Optimization

1. Background Processing

Process webhooks asynchronously:

import django_rq
from django.http import JsonResponse

@csrf_exempt
def webhook_handler(request):
# Immediately acknowledge receipt
payload = request.body.decode()
process_webhook_task.delay(payload)

return JsonResponse({"status": "received"})

@task
async def process_webhook_task(payload: str):
# Process in background
# ...
pass

2. Enable Compression

ngrok: NgrokConfig = NgrokConfig(
enabled=True,
tunnel=NgrokTunnelConfig(
compression=True # Faster for large payloads
)
)

3. Use Webhook Queues

# Queue webhooks for processing
from django.core.cache import cache

@csrf_exempt
def webhook_handler(request):
# Add to queue
webhook_id = str(uuid.uuid4())
cache.set(f"webhook:{webhook_id}", request.body, timeout=3600)

# Process asynchronously
process_webhook_queue.send(webhook_id)

return JsonResponse({"status": "queued"})

Security Considerations

1. Verify Webhook Signatures

Always verify webhook signatures from external services:

# Stripe signature verification
import stripe

@csrf_exempt
def stripe_webhook(request):
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']

try:
event = stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
)
except stripe.error.SignatureVerificationError:
return JsonResponse({"error": "Invalid signature"}, status=400)

# Process verified event
# ...

2. Use HTTPS Only

ngrok: NgrokConfig = NgrokConfig(
enabled=True,
tunnel=NgrokTunnelConfig(
bind_tls=True # Force HTTPS
)
)

3. Password Protect Tunnel (Testing)

ngrok: NgrokConfig = NgrokConfig(
enabled=True,
tunnel=NgrokTunnelConfig(
basic_auth=["admin:supersecret"] # Require authentication
)
)

Summary

Key Takeaways:

✅ Use runserver_ngrok instead of runserver ✅ Always use helper functions (get_webhook_url(), etc.) ✅ Check tunnel status before critical operations ✅ Log all webhook events for debugging ✅ Verify webhook signatures from external services ✅ Process webhooks asynchronously for better performance ✅ Use ngrok web interface (localhost:4040) for debugging ✅ Development only - never use in production


Next Steps

See Also