Skip to main content
An agent-assisted gateway bridges your localhost development environment with the public internet, enabling AI coding tools, webhook testing, and real authentication flows without waiting for production deployment. Instead of port forwarding and localhost limitations, you get a production-like environment for testing traffic patterns, auth flows, and integrations. With this setup, you can:
  • Expose local services securely to AI coding assistants and external tools
  • Test real OAuth flows, webhooks, and API integrations during development
  • Apply traffic transformations and header manipulation in your dev environment
  • Debug production-like traffic patterns before deploying
  • Share work-in-progress with teammates and stakeholders securely

1. Create internal endpoints for your local services

Start Agent Endpoints for your local development services using internal URLs. Replace $PORT with your actual service ports.
# Backend API (e.g., Express, Flask, FastAPI)
ngrok http $BACKEND_PORT --url https://api-dev.internal

# Frontend development server (e.g., React, Vue, Next.js)
ngrok http $FRONTEND_PORT --url https://app-dev.internal

2. Reserve a domain

Navigate to the Domains section of the ngrok dashboard and click New + to reserve a free static domain like https://your-service.ngrok.app or a custom domain you already own. We’ll refer to this domain as $NGROK_DOMAIN from here on out.
Consider using a subdomain like dev.yourcompany.com or staging.yourproject.com to clearly distinguish from production.

3. Create a Cloud Endpoint

Navigate to the Endpoints section of the ngrok dashboard, then click New + and Cloud Endpoint. In the URL field, enter the domain you just reserved to finish creating your Cloud Endpoint.

4. Apply Traffic Policy for development gateway

While still viewing your new cloud endpoint in the dashboard, copy and paste the policy below into the Traffic Policy editor. Make sure you change each of the following values:
  • $GITHUB_OAUTH_CLIENT_ID: Replace with your GitHub OAuth app client ID for development
  • $GIBHUB_OAUTH_CLIENT_SECRET: Replace with your GitHub OAuth app secret for development
  • $YOUR_DEV_API_KEY: Replace with an API key for development access
  • Internal service URLs: Replace with your actual internal endpoint URLs
on_http_request:
  # Development environment identification
  - actions:
      - type: add-headers
        config:
          headers:
            x-environment: "development"
            x-client-ip: "${conn.client_ip}"
            x-client-location: "${conn.geo.city}, ${conn.geo.country}"
            x-forwarded-host: "${req.host}"

  # OAuth authentication for protected development routes
  - expressions:
      - "req.url.path.startsWith('/admin') || req.url.path.startsWith('/dashboard')"
    actions:
      - type: oauth
        config:
          provider: google
          client_id: "$GITHUB_OAUTH_CLIENT_ID"
          client_secret: "$GITHUB_OAUTH_CLIENT_SECRET"
          scopes:
            - https://www.googleapis.com/auth/userinfo.email

  # API key authentication for external tools and AI assistants
  - expressions:
      - "req.url.path.startsWith('/api')"
      - "!hasReqHeader('authorization')"
    actions:
      - type: basic-auth
        config:
          credentials:
            - "dev-user:$YOUR_DEV_API_KEY"

  # Allow webhook testing without auth (for development only)
  - expressions:
      - "req.url.path.startsWith('/webhooks')"
    actions:
      - type: add-headers
        config:
          headers:
            x-webhook-source: "${req.user_agent}"
            x-webhook-timestamp: "${timestamp(time.now)}"
            x-webhook-signature: "${getReqHeader('x-signature')[0]}"

  # Route API requests to backend
  - expressions:
      - "req.url.path.startsWith('/api')"
    actions:
      - type: forward-internal
        config:
          url: https://api-dev.internal

  # Route webhook requests to backend
  - expressions:
      - "req.url.path.startsWith('/webhooks')"
    actions:
      - type: forward-internal
        config:
          url: https://api-dev.internal

  # Route admin/dashboard to backend with auth headers
  - expressions:
      - "req.url.path.startsWith('/admin') || req.url.path.startsWith('/dashboard')"
    actions:
      - type: add-headers
        config:
          headers:
            x-authenticated-user: "${actions.ngrok.oauth.identity.email}"
            x-auth-provider: "github"
      - type: forward-internal
        config:
          url: https://app-dev.internal

  # Default route everything else to frontend
  - actions:
      - type: forward-internal
        config:
          url: https://app-dev.internal

on_http_response:
  # Add development debugging headers
  - actions:
      - type: add-headers
        config:
          headers:
            x-served-by: "ngrok-dev-gateway"
            x-processing-timestamp: "${time.now}"

  # Enable CORS for development (be more restrictive in production)
  - actions:
      - type: add-headers
        config:
          headers:
            access-control-allow-origin: "*"
            access-control-allow-methods: "GET, POST, PUT, DELETE, OPTIONS, PATCH"
            access-control-allow-headers: "Content-Type, Authorization, X-API-Key"
            access-control-expose-headers: "X-Processing-Timestamp"
What’s happening here? On every HTTP request, the policy adds environment identification headers for tracing, enforces OAuth authentication for admin/dashboard routes, requires API key authentication for API routes accessed by external tools and AI assistants, and allows webhook testing without authentication for easy debugging. The gateway routes traffic based on URL paths—API requests go to your backend service, admin routes go to the backend with authentication headers, and everything else goes to your frontend. On responses, it adds development-specific headers for debugging, enables permissive CORS for local development, and timestamps responses for request tracking.

5. Try out your development gateway

Visit the domain you reserved either in the browser or in the terminal using a tool like curl. You should see the app or service at the port connected to your internal Agent Endpoint. Test different aspects of your development setup:
# Test your frontend application
curl "https://$NGROK_DOMAIN/"

# Test API with authentication (basic auth with base64 encoded API key)
# First, base64 encode the dev-user:$YOUR_DEV_API_KEY pair
# Example: dev-user:my-api-key becomes ZGV2LXVzZXI6bXktYXBpLWtleQ==
curl -H "Authorization: Basic ZGV2LXVzZXI6bXktYXBpLWtleQ==" \
     "https://$NGROK_DOMAIN/api/users"

# Test OAuth protected admin route (will redirect to GitHub)
curl "https://$NGROK_DOMAIN/admin/dashboard"
When using tools like Lovable, Cursor, or other AI coding assistants:
  1. They can access your API schema: https://$NGROK_DOMAIN/api/schema
  2. Test frontend builds against your real backend: https://$NGROK_DOMAIN/api/*
  3. Receive webhooks for real-time collaboration: https://$NGROK_DOMAIN/webhooks/ai-assist

What’s next?

I