QuotaGuard and Supabase Edge Functions Integration Guide

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

This page covers outbound traffic from Edge Functions to external services. An Edge Function reaching your own Supabase database does not need a proxy under default project settings.

That changes if Network Restrictions are enabled. Supabase’s documentation states that with network restrictions applied, Edge Functions lose direct access to the database.

The same applies from outside Supabase. If your application runs on Heroku, Render, Railway, Fly.io, or a VM and connects to a Supabase database with Network Restrictions enabled, that connection needs a static source IP to be allowlisted. QuotaGuard handles that case too.

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 paid add-on, 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.

Feature Supabase Dedicated IPv4 QuotaGuard
Direction Inbound (to database) Outbound (from functions)
Use case External services connecting to your DB Your functions connecting to external services
Edge Functions support No Yes
Static egress IPs No Yes

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 the APIs you are calling.

QuotaGuard proxies run in 12 AWS regions:

Region Location
US-East-1 N. Virginia
US-East-2 Ohio
US-West-2 Oregon
CA-Central-1 Montreal
EU-West-1 Ireland
EU-West-2 London
EU-Central-1 Frankfurt
AP-Northeast-1 Tokyo
AP-Southeast-1 Singapore
AP-Southeast-2 Sydney
AP-South-1 Mumbai
SA-East-1 Sao Paulo

Your proxy URL will look like this:

http://username:password@<region>-static-01.quotaguard.com:9293

For example, http://username:password@us-east-static-01.quotaguard.com:9293. Use the exact QUOTAGUARDSTATIC_URL value shown in your dashboard, not a hostname assembled by hand.

Finding Your Static IPs: The two static IPs assigned to your subscription are displayed in the QuotaGuard dashboard. Allowlist both of them on the target service. Use the pair shown in your dashboard as the single source of truth. Do not resolve the proxy hostname to obtain an address.

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@<region>-static-01.quotaguard.com:9293"

Use the exact QUOTAGUARDSTATIC_URL value from your QuotaGuard dashboard.

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

Deno.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();
  }
});

Protocol Variants: Static HTTP, Static SOCKS5, Shield HTTPS

The same code pattern works for all three QuotaGuard proxy protocols. Only the proxy URL scheme and port change:

Product Protocol URL Pattern
Static HTTP http://user:pass@<region>-static-01.quotaguard.com:9293
Static SOCKS5 socks5://user:pass@<region>-static-01.quotaguard.com:1080
Shield HTTPS https://user:pass@<region>-shield-01.quotaguard.com:9294

The Deno.createHttpClient configuration is the same shape in each case:

// Static HTTP (port 9293) - replace URL in the Basic Proxy Setup above
const client = Deno.createHttpClient({
  proxy: {
    url: "http://<your-quotaguard-proxy-host>:9293",
    basicAuth: { username: "...", password: "..." },
  },
});

// Static SOCKS5 (port 1080) - use for non-HTTP TCP or when you need SOCKS semantics
const client = Deno.createHttpClient({
  proxy: {
    url: "socks5://<your-quotaguard-proxy-host>:1080",
    basicAuth: { username: "...", password: "..." },
  },
});

// Shield HTTPS (port 9294) - encrypts proxy credentials and destination host:port on the wire
const client = Deno.createHttpClient({
  proxy: {
    url: "https://<your-quotaguard-shield-host>:9294",
    basicAuth: { username: "...", password: "..." },
  },
});

Use the exact host and credentials from your QuotaGuard dashboard in each case.

Note: Shield also provides a Secure SOCKS variant on port 1081, but that one requires the QGTunnel sidecar binary, which Supabase Edge Functions do not support. Use Static SOCKS5 if you need SOCKS on this platform.

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 { proxiedFetch } from "../_shared/proxy.ts";

Deno.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

Deno.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

Deno.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 the two static IPs from your QuotaGuard dashboard 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:

Deno.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

To create a tunnel on a platform that supports QGTunnel, go to Setup (the gear icon, top-right) > QGTunnel Configuration > Create a Tunnel in your QuotaGuard dashboard.

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

Deno.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": "203.0.113.10",
  "proxy_working": true
}

The returned IP should match one of the two static IPs shown in your QuotaGuard dashboard. Run it multiple times to see both IPs in use.

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@<region>-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

Local runs confirm your code and credentials are wired correctly. They do not confirm behavior on the hosted runtime, which has different network permissions. Deploy and invoke the function before relying on the result.

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.

A same-region proxy adds less latency than a cross-region one. Measure against your own workload and your own target API rather than assuming a fixed figure.

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-1 region.

Example of selective proxying:

Deno.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

Deno.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@<region>-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 (copy it from 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

A request that returns a normal 200 response but reports a non-QuotaGuard IP means the proxy configuration was ignored, not that it failed. Always check the returned IP against the pair in your dashboard rather than treating a successful response as confirmation.

Deno.createHttpClient Not Available

Deno.createHttpClient is available on current Supabase Edge Runtime versions (verified on v1.73.x with HTTP, SOCKS5, and HTTPS proxies). If you see an error that it is not available, the runtime is likely out of date. Redeploy your function to pick up the latest runtime, or upgrade your Supabase CLI if you are self-hosting. If the problem persists on the current runtime, contact QuotaGuard support.

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@us-east-static-01.quotaguard.com:9293"

# Bad - Don't do this in code
const proxyUrl = "http://user:pass@us-east-static-01.quotaguard.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

Feature QuotaGuard Static QuotaGuard Shield
Protocol HTTP / HTTPS / SOCKS5 HTTPS / SOCKS5 over TLS
Customer-to-proxy hop Plaintext TLS-encrypted
HTTPS payload Tunneled end-to-end, never decrypted at the proxy Tunneled end-to-end, never decrypted at the proxy
Best for Most apps Regulated data or environments that require TLS on every hop
Starting price (direct) $19/month $29/month

Static is right for most apps on Supabase Edge Functions. Choose Shield if the workload handles regulated data under HIPAA, PCI-DSS, or SOC 2, or if the environment requires TLS between your app and the proxy itself.


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

Back to top ↑

Copyright © 2009 - 2026 QuotaGuard. All rights reserved.

Copyright © 2009 - 2026 QuotaGuard. All rights reserved.