Skip to 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