Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Voximplant -> Vapi Proxy

Small Node.js proxy that converts Voximplant WebSocket media protocol to Vapi WebSocket Transport.

The service can use a default Vapi assistant from VAPI_ASSISTANT_ID, or a request can pass assistantId to route a call to a specific assistant.

Flow

PSTN call -> Voximplant -> this proxy -> Vapi WebSocket Transport -> Vapi Assistant

Voximplant side:

JSON start/media/stop with base64 audio payload

Vapi side:

raw binary mulaw/8000 audio frames

Downlink note: observed Vapi WebSocket binary audio is playable as mulaw/8000, while Voximplant starts WebSocket playback as PCM16. The proxy decodes Vapi mulaw/8000 and sends Voximplant playback as PCM16/16000.

Setup

npm install
cp .env.example .env

Edit .env:

VAPI_API_KEY=...
VAPI_ASSISTANT_ID=... # optional default assistant
VAPI_API_URL=https://api.vapi.ai
PORT=8000
PUBLIC_BASE_URL=https://proxy.example.com
VAPI_OUTPUT_SAMPLE_RATE=8000

Run:

npm start

Health check:

curl http://localhost:8000/health

API

POST /start

Creates a Vapi WebSocket call and returns a Voximplant-facing WebSocket URL.

Diagnostic replay:

wss://proxy.example.com/test/replay-last

This replays the latest saved vapi-downlink.raw file to Voximplant as PCM16/16000 without creating a Vapi call.

Request body can be empty:

{}

Optional metadata:

{
  "assistantId": "your_vapi_assistant_id",
  "metadata": {
    "caller": "375...",
    "destination": "375..."
  },
  "assistantOverrides": {
    "variableValues": {
      "callerPhone": "375...",
      "destinationPhone": "375..."
    }
  }
}

If assistantId is omitted, the proxy uses VAPI_ASSISTANT_ID from .env.

Response:

{
  "callId": "...",
  "sessionId": "...",
  "status": "queued",
  "voxWebSocketUrl": "wss://proxy.example.com/vox/...",
  "audioFormat": {
    "format": "mulaw",
    "container": "raw",
    "sampleRate": 8000
  }
}

VoxEngine Example

require(Modules.WebSocket);

const PROXY_START_URL = 'https://proxy.example.com/start';

VoxEngine.addEventListener(AppEvents.CallAlerting, async ({ call }) => {
  call.answer();

  const response = await Net.httpRequestAsync(PROXY_START_URL, {
    method: 'POST',
    headers: ['Content-Type: application/json'],
    postData: JSON.stringify({
      assistantId: 'your_vapi_assistant_id',
      metadata: {
        caller: call.callerid(),
        destination: call.number()
      },
      assistantOverrides: {
        variableValues: {
          callerPhone: call.callerid(),
          destinationPhone: call.number()
        }
      }
    })
  });

  const session = JSON.parse(response.text);
  const ws = VoxEngine.createWebSocket(session.voxWebSocketUrl);

  ws.addEventListener(WebSocketEvents.OPEN, () => {
    call.sendMediaTo(ws, { encoding: WebSocketAudioEncoding.ULAW });
    ws.sendMediaTo(call);
  });

  ws.addEventListener(WebSocketEvents.ERROR, (event) => {
    Logger.write('Proxy WS error: ' + JSON.stringify(event));
  });

  ws.addEventListener(WebSocketEvents.CLOSE, (event) => {
    Logger.write('Proxy WS closed: ' + JSON.stringify(event));
    VoxEngine.terminate();
  });

  call.addEventListener(CallEvents.Disconnected, () => {
    ws.close();
    VoxEngine.terminate();
  });
});

Nginx WebSocket Proxy

server {
    listen 80;
    server_name proxy.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

Then issue TLS:

sudo certbot --nginx -d proxy.example.com

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages