Proxy server guide

Keep assistant configs and API keys on your backend. Route Web SDK calls through your proxy.

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

frontend.js
1import Vapi from '@vapi-ai/web';
2
3const vapi = new Vapi('your-token', 'https://your-proxy.com');
4// Second parameter is your backend/proxy service URL. Without this, calls route to Vapi API directly.
5
6vapi.start({
7 userId: 'customer123',
8 assistantType: 'sales-coach',
9 // Send any custom data your backend needs
10});

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)

cloudflare-worker.js
1export default {
2 async fetch(request, env, ctx) {
3 const url = new URL(request.url);
4
5 // Basic CORS support (adjust origins/headers to your needs)
6 const corsHeaders = {
7 'Access-Control-Allow-Origin': '*',
8 'Access-Control-Allow-Methods': 'POST, OPTIONS',
9 'Access-Control-Allow-Headers': 'Content-Type, Authorization'
10 };
11
12 if (request.method === 'OPTIONS') {
13 return new Response(null, { status: 204, headers: corsHeaders });
14 }
15
16 // Handle Vapi API calls
17 if (url.pathname.startsWith('/call')) {
18 try {
19 const { userId, assistantType, ...rest } = await request.json();
20 const assistantConfig = getAssistantConfig(userId, assistantType, rest);
21
22 const response = await fetch(`https://api.vapi.ai${url.pathname}`, {
23 method: 'POST',
24 headers: {
25 Authorization: `Bearer ${env.VAPI_API_KEY}`,
26 'Content-Type': 'application/json',
27 },
28 body: JSON.stringify(assistantConfig),
29 });
30
31 return new Response(response.body, {
32 status: response.status,
33 headers: { 'Content-Type': 'application/json', ...corsHeaders },
34 });
35 } catch (error) {
36 return new Response(JSON.stringify({ error: 'Proxy error', details: String(error) }), {
37 status: 500,
38 headers: { 'Content-Type': 'application/json', ...corsHeaders },
39 });
40 }
41 }
42
43 return new Response('Proxy running', { headers: corsHeaders });
44 },
45};
46
47function getAssistantConfig(userId, assistantType) {
48 if (assistantType === 'existing') {
49 return { assistantId: 'YOUR_ASSISTANT_ID' };
50 }
51
52 return {
53 assistant: {
54 // Your transient assistant config here
55 },
56 };
57}
1

Extract custom data from the request

Parse and validate the fields your frontend sends (e.g., userId, assistantType), plus any other context you need.

2

Map to assistant configuration

Choose a permanent assistant ID or build a transient assistant configuration based on the request.

3

Call Vapi and return the response

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.