Netlify edge function
Netlify redirects with status 200 cannot add headers, so the proxy runs as an edge function.
Setup
X3_API_ORIGIN={{API_ORIGIN}}
{{PROXY_KEY_ENV}}=sk_3x_YOUR_PROXY_KEYSet both variables in Site configuration → Environment variables as well (scope: Functions).
import type { Config, Context } from "@netlify/edge-functions";
const PREFIX = "{{PROXY_PATH}}";
const ALLOWED = /^\/v1\/(?:e|collect|cb|c\/[^/]+|s\/[A-Za-z0-9@._-]+)$/;
export default async (request: Request, context: Context): Promise<Response> => {
const url = new URL(request.url);
const path = url.pathname.slice(PREFIX.length);
if (!ALLOWED.test(path)) return new Response("not found", { status: 404 });
const headers = new Headers();
for (const name of ["user-agent", "origin", "content-type", "accept", "sec-fetch-site", "sec-gpc", "dnt"]) {
const value = request.headers.get(name);
if (value) headers.set(name, value);
}
headers.set("x-3x-client-ip", context.ip);
headers.set("x-3x-client-country", context.geo.country?.code ?? "");
headers.set("x-3x-proxy-key", Netlify.env.get("{{PROXY_KEY_ENV}}") ?? "");
const upstream = await fetch(new URL(path + url.search, Netlify.env.get("X3_API_ORIGIN")), {
method: request.method,
headers,
body: request.method === "POST" ? await request.arrayBuffer() : undefined,
});
const response = new Response(upstream.body, upstream);
response.headers.delete("set-cookie");
if (path.startsWith("/v1/s/") && upstream.ok) response.headers.set("cache-control", "public, max-age=3600, stale-while-revalidate=86400");
return response;
};
export const config: Config = { path: "{{PROXY_PATH}}/*" };Deploy as usual (netlify deploy --prod or a Git push).
Verify
curl -s -o /dev/null -w "%{http_code}\n" https://YOUR-SITE{{PROXY_PATH}}/v1/s/x3.jsPrints 200; POST {{PROXY_PATH}}/v1/e from a page returns 204. context.ip is the client address
as seen by Netlify's edge and cannot be spoofed by the visitor.