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.

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 your primary user base or the APIs you’re calling.

Primary User/API Location Recommended QuotaGuard Region
United States (East Coast) US-East
United States (West Coast) US-West
Europe EU (Ireland or Frankfurt)
Asia-Pacific AP-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.

Configuration Added Latency
Edge Function + Same Region Proxy 20-50ms
Edge Function + Cross-Region Proxy 50-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:

Feature QuotaGuard Static QuotaGuard Shield
Protocol HTTP/SOCKS5 HTTPS/SOCKS5 over TLS
Encryption Standard proxy SSL Passthrough (E2EE)
Best for General API access HIPAA, 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

Back to top ↑

Copyright © 2009 - 2026 QuotaGuard. All rights reserved.