QuotaGuard and Supabase Edge Functions Integration Guide

    QuotaGuard and Supabase Edge Functions Integration Guide

    QuotaGuard Static IPs allow your Supabase Edge Functions to send outbound HTTP requests through a load-balanced pair of static IP addresses. Once configured, you can use QuotaGuard’s IPs to connect to firewalled APIs, enterprise databases, and services that require IP allowlisting.

    You do not need QuotaGuard for internal Supabase operations. Connections to your Supabase database, storage, and other Supabase services are handled internally. QuotaGuard is specifically for reaching external services that require a known, static source IP address.

    Why Supabase Edge Functions Need Static IPs

    Supabase Edge Functions run on Deno Deploy infrastructure distributed globally across edge locations. Your function’s outbound traffic can originate from any of these locations, and Supabase explicitly does not publish outbound IP ranges.

    From Supabase’s documentation on IP addresses:

    “We don’t publish the IP addresses for our Rest, Storage, or Realtime services because their outgoing IP addresses can change without prior notice.”

    This creates problems when your Edge Function needs to connect to:

    • Enterprise APIs with firewall allowlisting requirements
    • MongoDB Atlas or other databases with IP-based network access controls
    • Payment providers like PayPro Global or banking APIs
    • Internal corporate APIs behind firewalls
    • Google Workspace with Context-Aware Access policies
    • Legacy systems that only accept connections from known IP addresses

    QuotaGuard gives your Edge Functions a fixed, verifiable IP identity that partners can add to their firewall allowlists once.

    Native Supabase IP Options (What They Don’t Cover)

    Supabase offers a Dedicated IPv4 Address add-on ($4/month), but this is for inbound connections only. It provides a static IP for connecting TO your Supabase database from external services.

    This does NOT provide static outbound IPs for your Edge Functions. Traffic FROM Edge Functions to external services still uses dynamic, shared IP addresses.

    FeatureSupabase Dedicated IPv4QuotaGuard
    DirectionInbound (to database)Outbound (from functions)
    Use caseExternal services connecting to your DBYour functions connecting to external services
    Edge Functions supportNoYes
    Static egress IPsNoYes

    Getting Started

    After creating a QuotaGuard account, you will be redirected to your dashboard where you can find your proxy credentials and static IP addresses.

    Choose the right proxy region: Supabase Edge Functions run globally at edge locations closest to your users. For lowest latency on proxied requests, select a QuotaGuard region closest to your primary user base or the APIs you’re calling.

    Primary User/API LocationRecommended QuotaGuard Region
    United States (East Coast)US-East
    United States (West Coast)US-West
    EuropeEU (Ireland or Frankfurt)
    Asia-PacificAP-Southeast (Singapore) or AP-Northeast (Tokyo)

    Your proxy URL will look like this:

    http://username:password@us-east-static-01.quotaguard.com:9293
    

    Finding Your Static IPs: Your two static IPs are displayed in the QuotaGuard dashboard. Both IPs are active simultaneously for high availability. Add both to any firewall allowlists you’re configuring on the target service side.

    Configuring Your Edge Function

    Step 1: Add Your Proxy URL as a Secret

    Store your QuotaGuard credentials securely using Supabase secrets:

    supabase secrets set QUOTAGUARDSTATIC_URL="http://username:password@us-east-static-01.quotaguard.com:9293"
    

    Verify the secret was set:

    supabase secrets list
    

    You can also add secrets through the Supabase Dashboard under Project Settings > Edge Functions > Secrets.

    Step 2: Configure Your HTTP Requests with the Proxy

    Supabase Edge Functions run on a Deno-compatible runtime. Deno provides Deno.createHttpClient() which supports proxy configuration for the standard fetch() API.

    Basic Proxy Setup

    // supabase/functions/my-function/index.ts
    import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
    
    serve(async (req) => {
      // Get proxy URL from secrets
      const proxyUrl = Deno.env.get("QUOTAGUARDSTATIC_URL");
      
      if (!proxyUrl) {
        return new Response(
          JSON.stringify({ error: "Proxy URL not configured" }),
          { status: 500, headers: { "Content-Type": "application/json" } }
        );
      }
    
      // Parse proxy URL to extract credentials
      const url = new URL(proxyUrl);
      
      // Create HTTP client with proxy configuration
      const client = Deno.createHttpClient({
        proxy: {
          url: `${url.protocol}//${url.host}`,
          basicAuth: {
            username: url.username,
            password: url.password,
          },
        },
      });
    
      try {
        // Make the proxied request
        const response = await fetch("https://api.example.com/data", { client });
        const data = await response.json();
    
        return new Response(
          JSON.stringify(data),
          { headers: { "Content-Type": "application/json" } }
        );
      } catch (error) {
        return new Response(
          JSON.stringify({ error: error.message }),
          { status: 500, headers: { "Content-Type": "application/json" } }
        );
      } finally {
        // Always close the client when done
        client.close();
      }
    });
    

    Reusable Proxy Helper Function

    For functions that make multiple proxied requests, create a helper:

    // supabase/functions/_shared/proxy.ts
    
    export interface ProxyConfig {
      proxyUrl: string;
    }
    
    export function createProxiedClient(): Deno.HttpClient {
      const proxyUrl = Deno.env.get("QUOTAGUARDSTATIC_URL");
      
      if (!proxyUrl) {
        throw new Error("QUOTAGUARDSTATIC_URL not configured");
      }
    
      const url = new URL(proxyUrl);
      
      return Deno.createHttpClient({
        proxy: {
          url: `${url.protocol}//${url.host}`,
          basicAuth: {
            username: url.username,
            password: url.password,
          },
        },
      });
    }
    
    export async function proxiedFetch(
      input: string | URL | Request,
      init?: RequestInit
    ): Promise<Response> {
      const client = createProxiedClient();
      
      try {
        return await fetch(input, { ...init, client });
      } finally {
        client.close();
      }
    }
    

    Usage:

    // supabase/functions/my-function/index.ts
    import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
    import { proxiedFetch } from "../_shared/proxy.ts";
    
    serve(async (req) => {
      const response = await proxiedFetch("https://api.example.com/data");
      const data = await response.json();
      
      return new Response(JSON.stringify(data), {
        headers: { "Content-Type": "application/json" },
      });
    });
    

    POST Request with JSON Body

    import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
    
    serve(async (req) => {
      const proxyUrl = Deno.env.get("QUOTAGUARDSTATIC_URL");
      const url = new URL(proxyUrl!);
      
      const client = Deno.createHttpClient({
        proxy: {
          url: `${url.protocol}//${url.host}`,
          basicAuth: {
            username: url.username,
            password: url.password,
          },
        },
      });
    
      try {
        const payload = await req.json();
        
        const response = await fetch("https://api.example.com/submit", {
          client,
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${Deno.env.get("API_TOKEN")}`,
          },
          body: JSON.stringify(payload),
        });
    
        const result = await response.json();
        
        return new Response(JSON.stringify(result), {
          headers: { "Content-Type": "application/json" },
        });
      } finally {
        client.close();
      }
    });
    

    Request with Custom Headers

    import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
    
    serve(async (req) => {
      const proxyUrl = Deno.env.get("QUOTAGUARDSTATIC_URL");
      const url = new URL(proxyUrl!);
      
      const client = Deno.createHttpClient({
        proxy: {
          url: `${url.protocol}//${url.host}`,
          basicAuth: {
            username: url.username,
            password: url.password,
          },
        },
      });
    
      try {
        const response = await fetch("https://api.example.com/protected", {
          client,
          headers: {
            "Authorization": `Bearer ${Deno.env.get("API_KEY")}`,
            "X-Custom-Header": "custom-value",
            "Accept": "application/json",
          },
        });
    
        if (!response.ok) {
          return new Response(
            JSON.stringify({ error: `API returned ${response.status}` }),
            { status: response.status, headers: { "Content-Type": "application/json" } }
          );
        }
    
        const data = await response.json();
        
        return new Response(JSON.stringify(data), {
          headers: { "Content-Type": "application/json" },
        });
      } finally {
        client.close();
      }
    });
    

    Common Use Cases

    Connecting to Firewalled APIs

    Many enterprise APIs require IP allowlisting. Configure the proxy in your Edge Function, then provide your QuotaGuard static IPs to the API provider.

    // Example: Calling a partner API that requires IP whitelisting
    const response = await proxiedFetch("https://partner-api.example.com/v1/data", {
      headers: {
        "Authorization": `Bearer ${Deno.env.get("PARTNER_API_KEY")}`,
      },
    });
    

    Payment Provider Integrations

    Financial services APIs often have strict IP-based access controls:

    // Example: Calling a payment provider that requires static IPs
    const response = await proxiedFetch("https://api.payprovider.com/v1/transactions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-API-Key": Deno.env.get("PAYMENT_API_KEY")!,
      },
      body: JSON.stringify({
        amount: 1000,
        currency: "USD",
        // ... transaction details
      }),
    });
    

    Database HTTP APIs

    While Edge Functions can connect to your Supabase database directly, you may need to connect to external databases that offer HTTP APIs:

    // Example: MongoDB Atlas Data API
    const response = await proxiedFetch(
      "https://data.mongodb-api.com/app/data-xxxxx/endpoint/data/v1/action/find",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "api-key": Deno.env.get("MONGODB_API_KEY")!,
        },
        body: JSON.stringify({
          dataSource: "Cluster0",
          database: "mydb",
          collection: "users",
          filter: { status: "active" },
        }),
      }
    );
    

    Webhook Delivery to Firewalled Endpoints

    When your Edge Function needs to deliver webhooks to systems with IP restrictions:

    serve(async (req) => {
      const event = await req.json();
      
      // Deliver webhook to a firewalled endpoint
      const response = await proxiedFetch("https://internal.partner.com/webhooks/receive", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Webhook-Signature": computeSignature(event),
        },
        body: JSON.stringify(event),
      });
    
      if (!response.ok) {
        // Log failure, implement retry logic, etc.
        console.error(`Webhook delivery failed: ${response.status}`);
      }
    
      return new Response("OK", { status: 200 });
    });
    

    Database Connections

    Supabase Edge Functions are primarily designed for HTTP workloads. For non-HTTP database connections (PostgreSQL, MySQL, MongoDB via native drivers), the serverless nature of Edge Functions creates challenges.

    1. Use HTTP/REST APIs when available (MongoDB Atlas Data API, Supabase’s own REST API, etc.)

    2. Use Supabase’s built-in database for primary data storage. Your Edge Function can access your Supabase PostgreSQL database directly without needing a proxy.

    3. For external PostgreSQL/MySQL, consider:

      • Using the database’s HTTP API if available
      • Creating a dedicated API service on a platform that supports QGTunnel (like Heroku, Fly.io, or Render) that proxies database queries

    Connecting to External PostgreSQL via HTTP Proxy

    If your external database offers an HTTP interface:

    // Example: Connecting to Neon's serverless driver (uses HTTP)
    const response = await proxiedFetch("https://your-db.neon.tech/sql", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${Deno.env.get("NEON_API_KEY")}`,
      },
      body: JSON.stringify({
        query: "SELECT * FROM users WHERE active = true",
      }),
    });
    

    Testing Your Implementation

    Verify Your Static IP

    Create a test function to verify your proxy configuration:

    // supabase/functions/test-proxy/index.ts
    import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
    
    serve(async (req) => {
      const proxyUrl = Deno.env.get("QUOTAGUARDSTATIC_URL");
      
      if (!proxyUrl) {
        return new Response(
          JSON.stringify({ error: "QUOTAGUARDSTATIC_URL not set" }),
          { status: 500, headers: { "Content-Type": "application/json" } }
        );
      }
    
      const url = new URL(proxyUrl);
      
      const client = Deno.createHttpClient({
        proxy: {
          url: `${url.protocol}//${url.host}`,
          basicAuth: {
            username: url.username,
            password: url.password,
          },
        },
      });
    
      try {
        // ip.quotaguard.com returns the client's IP address
        const response = await fetch("https://ip.quotaguard.com", { client });
        const data = await response.json();
    
        return new Response(
          JSON.stringify({
            your_static_ip: data.ip,
            proxy_working: true,
          }),
          { headers: { "Content-Type": "application/json" } }
        );
      } catch (error) {
        return new Response(
          JSON.stringify({
            error: error.message,
            proxy_working: false,
          }),
          { status: 500, headers: { "Content-Type": "application/json" } }
        );
      } finally {
        client.close();
      }
    });
    

    Deploy and invoke:

    supabase functions deploy test-proxy
    supabase functions invoke test-proxy
    

    Expected response:

    {
      "your_static_ip": "52.34.188.175",
      "proxy_working": true
    }
    

    The returned IP should match one of your two static IPs shown in the QuotaGuard dashboard. Run it multiple times to see both IPs in action (load-balanced).

    Local Development Testing

    When testing locally with supabase functions serve, you need to set your secrets:

    # Set secret for local development
    supabase secrets set QUOTAGUARDSTATIC_URL="http://username:password@us-east-static-01.quotaguard.com:9293" --local
    
    # Start the functions server
    supabase functions serve
    

    Then invoke your function:

    curl http://localhost:54321/functions/v1/test-proxy
    

    Latency Considerations

    Using QuotaGuard adds a network hop to your requests. This is especially relevant for Edge Functions, which are designed for low-latency responses.

    ConfigurationAdded Latency
    Edge Function + Same Region Proxy20-50ms
    Edge Function + Cross-Region Proxy50-150ms

    Important tradeoffs:

    1. Edge benefits may be reduced for proxied requests. Non-proxied requests benefit from edge distribution. Proxied requests route through QuotaGuard’s infrastructure.

    2. Only proxy requests that require static IPs. Keep non-firewalled API calls using standard fetch() without the proxy.

    3. Match proxy region to API location, not user location. If you’re calling an API hosted in US-East, use QuotaGuard’s US-East proxy.

    Example of selective proxying:

    serve(async (req) => {
      // Non-proxied request to public API (benefits from edge)
      const publicData = await fetch("https://api.publicservice.com/data");
      
      // Proxied request to firewalled API (needs static IP)
      const privateData = await proxiedFetch("https://partner.firewalled.com/secure");
      
      return new Response(JSON.stringify({
        public: await publicData.json(),
        private: await privateData.json(),
      }));
    });
    

    Error Handling

    Robust Error Handling Pattern

    import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
    
    serve(async (req) => {
      const proxyUrl = Deno.env.get("QUOTAGUARDSTATIC_URL");
      
      if (!proxyUrl) {
        console.error("QUOTAGUARDSTATIC_URL not configured");
        return new Response(
          JSON.stringify({ error: "Proxy configuration missing" }),
          { status: 500, headers: { "Content-Type": "application/json" } }
        );
      }
    
      let client: Deno.HttpClient | null = null;
      
      try {
        const url = new URL(proxyUrl);
        
        client = Deno.createHttpClient({
          proxy: {
            url: `${url.protocol}//${url.host}`,
            basicAuth: {
              username: url.username,
              password: url.password,
            },
          },
        });
    
        const response = await fetch("https://api.example.com/data", { client });
    
        if (!response.ok) {
          console.error(`API returned ${response.status}: ${await response.text()}`);
          return new Response(
            JSON.stringify({ error: `Upstream API error: ${response.status}` }),
            { status: response.status, headers: { "Content-Type": "application/json" } }
          );
        }
    
        const data = await response.json();
        
        return new Response(JSON.stringify(data), {
          headers: { "Content-Type": "application/json" },
        });
    
      } catch (error) {
        console.error("Request failed:", error);
        
        // Provide specific error messages for common issues
        if (error.message.includes("407")) {
          return new Response(
            JSON.stringify({ error: "Proxy authentication failed" }),
            { status: 500, headers: { "Content-Type": "application/json" } }
          );
        }
        
        if (error.message.includes("connection")) {
          return new Response(
            JSON.stringify({ error: "Connection to proxy failed" }),
            { status: 503, headers: { "Content-Type": "application/json" } }
          );
        }
    
        return new Response(
          JSON.stringify({ error: "Request failed" }),
          { status: 500, headers: { "Content-Type": "application/json" } }
        );
        
      } finally {
        if (client) {
          client.close();
        }
      }
    });
    

    Troubleshooting

    407 Proxy Authentication Required

    Your proxy credentials are incorrect. Verify:

    1. The secret is set correctly:
      supabase secrets list
      
    2. The URL format includes credentials:
      http://username:password@us-east-static-01.quotaguard.com:9293
      
    3. Check for special characters in your password that may need URL encoding.

    Connection Timeout

    1. Verify the QuotaGuard proxy hostname is correct (check your dashboard)
    2. Ensure port 9293 is used for HTTP proxy connections
    3. Check if the target service is reachable from QuotaGuard’s infrastructure

    Wrong IP Address Returned

    If ip.quotaguard.com returns an unexpected IP:

    1. Verify the proxy configuration is being applied to the specific fetch call
    2. Check that you’re passing the client option to fetch()
    3. Ensure you’re not accidentally using a non-proxied fetch

    Deno.createHttpClient Not Available

    If you see an error that Deno.createHttpClient is not available, this may be due to Deno Deploy/Supabase Edge Runtime limitations. Try using the standard HTTP_PROXY environment variable approach:

    // Alternative approach using environment variables
    // Note: This may not work in all Supabase Edge Runtime versions
    Deno.env.set("HTTP_PROXY", Deno.env.get("QUOTAGUARDSTATIC_URL")!);
    Deno.env.set("HTTPS_PROXY", Deno.env.get("QUOTAGUARDSTATIC_URL")!);
    
    const response = await fetch("https://api.example.com/data");
    

    If neither approach works, contact QuotaGuard support for alternative solutions.

    Function Timeout

    Supabase Edge Functions have execution time limits. If your proxied requests are timing out:

    1. Check if the target API is responding slowly
    2. Consider increasing timeout handling in your code
    3. For long-running operations, consider using Supabase’s background tasks or a different architecture

    Security Best Practices

    Never Hard-Code Credentials

    Always use Supabase secrets:

    # Good
    supabase secrets set QUOTAGUARDSTATIC_URL="http://user:pass@proxy.com:9293"
    
    # Bad - Don't do this in code
    const proxyUrl = "http://user:pass@proxy.com:9293"; // Never hard-code!
    

    Validate Proxy Configuration

    Always check that the proxy URL is configured before using it:

    const proxyUrl = Deno.env.get("QUOTAGUARDSTATIC_URL");
    if (!proxyUrl) {
      throw new Error("Proxy not configured");
    }
    

    Close HttpClient Connections

    Always close the HttpClient when done to prevent resource leaks:

    const client = Deno.createHttpClient({ /* ... */ });
    try {
      // Use client
    } finally {
      client.close();
    }
    

    QuotaGuard Static vs QuotaGuard Shield

    QuotaGuard offers two products for static IPs:

    FeatureQuotaGuard StaticQuotaGuard Shield
    ProtocolHTTP/SOCKS5HTTPS/SOCKS5 over TLS
    EncryptionStandard proxySSL Passthrough (E2EE)
    Best forGeneral API accessHIPAA, PCI-DSS, regulated data
    Starting price$19/month$69/month

    For most Supabase Edge Functions, QuotaGuard Static provides everything you need. Choose Shield if you’re handling protected health information (PHI), payment card data, or have specific compliance requirements.


    Ready to Get Started?

    Get in touch or create a free trial account.

    Get QuotaGuard for Supabase Edge Functions

    View Supabase Integration Features

    Contact Support


    Ready to Get Started?

    Get in touch or create a free trial account