QuotaGuard and Supabase Edge Functions Integration Guide
Table of contents
- QuotaGuard and Supabase Edge Functions Integration Guide
- Why Supabase Edge Functions Need Static IPs
- Native Supabase IP Options (What They Don’t Cover)
- Getting Started
- Configuring Your Edge Function
- Common Use Cases
- Database Connections
- Testing Your Implementation
- Latency Considerations
- Error Handling
- Troubleshooting
- Security Best Practices
- QuotaGuard Static vs QuotaGuard Shield
- Ready to Get Started?
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.
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
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:
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 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:
- The secret is set correctly:
supabase secrets list - The URL format includes credentials:
http://username:password@us-east-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 (check 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
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:
- 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@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