diff --git a/fern/docs.yml b/fern/docs.yml
index 8be66c40c..9ff7e8b76 100644
--- a/fern/docs.yml
+++ b/fern/docs.yml
@@ -603,6 +603,8 @@ navigation:
path: security-and-privacy/hipaa.mdx
- page: PCI compliance
path: security-and-privacy/PCI.mdx
+ - page: Proxy server guide
+ path: security-and-privacy/proxy-server.mdx
- link: SOC-2 Compliance
href: https://security.vapi.ai/
- section: Legal
diff --git a/fern/security-and-privacy/proxy-server.mdx b/fern/security-and-privacy/proxy-server.mdx
new file mode 100644
index 000000000..d96204000
--- /dev/null
+++ b/fern/security-and-privacy/proxy-server.mdx
@@ -0,0 +1,115 @@
+---
+title: Proxy server guide
+subtitle: Keep assistant configs and API keys on your backend. Route Web SDK calls through your proxy.
+slug: security-and-privacy/proxy-server
+---
+
+## Overview
+
+Proxy server keeps assistant configs and API keys on your backend. Frontend sends custom data, backend maps to Vapi calls.
+
+**Flow**: Frontend -> Your Proxy -> Vapi API -> Response -> Frontend
+
+Never expose your private API key in the browser. Keep it on your server and read it from environment variables.
+
+For public web clients, consider using JWT authentication to further restrict client capabilities.
+
+## Frontend setup
+
+```javascript title="frontend.js"
+import Vapi from '@vapi-ai/web';
+
+const vapi = new Vapi('your-token', 'https://your-proxy.com');
+// Second parameter is your backend/proxy service URL. Without this, calls route to Vapi API directly.
+
+vapi.start({
+ userId: 'customer123',
+ assistantType: 'sales-coach',
+ // Send any custom data your backend needs
+});
+```
+
+
+ The frontend passes only non-sensitive context (e.g., userId, assistant type). Your backend selects the actual assistant configuration and authenticates to Vapi.
+
+
+## Backend proxy server (example)
+
+```javascript title="cloudflare-worker.js" wordWrap
+export default {
+ async fetch(request, env, ctx) {
+ const url = new URL(request.url);
+
+ // Basic CORS support (adjust origins/headers to your needs)
+ const corsHeaders = {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization'
+ };
+
+ if (request.method === 'OPTIONS') {
+ return new Response(null, { status: 204, headers: corsHeaders });
+ }
+
+ // Handle Vapi API calls
+ if (url.pathname.startsWith('/call')) {
+ try {
+ const { userId, assistantType, ...rest } = await request.json();
+ const assistantConfig = getAssistantConfig(userId, assistantType, rest);
+
+ const response = await fetch(`https://api.vapi.ai${url.pathname}`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${env.VAPI_API_KEY}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(assistantConfig),
+ });
+
+ return new Response(response.body, {
+ status: response.status,
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
+ });
+ } catch (error) {
+ return new Response(JSON.stringify({ error: 'Proxy error', details: String(error) }), {
+ status: 500,
+ headers: { 'Content-Type': 'application/json', ...corsHeaders },
+ });
+ }
+ }
+
+ return new Response('Proxy running', { headers: corsHeaders });
+ },
+};
+
+function getAssistantConfig(userId, assistantType) {
+ if (assistantType === 'existing') {
+ return { assistantId: 'YOUR_ASSISTANT_ID' };
+ }
+
+ return {
+ assistant: {
+ // Your transient assistant config here
+ },
+ };
+}
+```
+
+
+
+ Parse and validate the fields your frontend sends (e.g., userId, assistantType), plus any other context you need.
+
+
+ Choose a permanent assistant ID or build a transient assistant configuration based on the request.
+
+
+ Authenticate to Vapi with your server-side API key and stream/forward the response to the client.
+
+
+
+Result: Secure calls with configs and secrets hidden on your backend.
+
+### Related
+
+- JWT authentication
+- Server URLs
\ No newline at end of file