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