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.
Recommended Approaches for External Databases
-
Use HTTP/REST APIs when available (MongoDB Atlas Data API, Supabase’s own REST API, etc.)
-
Use Supabase’s built-in database for primary data storage. Your Edge Function can access your Supabase PostgreSQL database directly without needing a proxy.
-
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:
-
Edge benefits may be reduced for proxied requests. Non-proxied requests benefit from edge distribution. Proxied requests route through QuotaGuard’s infrastructure.
-
Only proxy requests that require static IPs. Keep non-firewalled API calls using standard fetch() without the proxy.
-
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:
- The secret is set correctly:
supabase secrets list - The URL format includes credentials:
http://username:password@<region>-static-01.quotaguard.com:9293 - Check for special characters in your password that may need URL encoding.
Connection Timeout
- Verify the QuotaGuard proxy hostname is correct (copy it from your dashboard)
- Ensure port 9293 is used for HTTP proxy connections
- Check if the target service is reachable from QuotaGuard’s infrastructure
Wrong IP Address Returned
If ip.quotaguard.com returns an unexpected IP:
- Verify the proxy configuration is being applied to the specific fetch call
- Check that you’re passing the
clientoption tofetch() - 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:
- Check if the target API is responding slowly
- Consider increasing timeout handling in your code
- 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