*** ## title: 'Example: Placing an outbound call' This example starts a VoxEngine session, places an outbound PSTN call, and bridges audio to Cartesia Line Agents once the callee answers. **Jump to the [Full VoxEngine scenario](#full-voxengine-scenario).** ## Prerequisites * Create a Cartesia Line Agent in the [Cartesia Voice Agents portal](https://play.cartesia.ai/agents) * Store your [Cartesia API key](https://play.cartesia.ai/keys) in Voximplant `ApplicationStorage` under `CARTESIA_API_KEY` * Store your [Cartesia Agent ID](https://play.cartesia.ai/agents) in Voximplant `ApplicationStorage` under `CARTESIA_AGENT_ID` * Ensure outbound calling is enabled for your Voximplant application and your caller ID is verified ## Outbound call parameters The example expects destination and caller ID in `customData` passed to the routing rule: ```json title="Custom data example" {"destination":"+15551234567","callerId":"+15557654321"} ``` ## Launch the routing rule For quick testing from the Voximplant Control Panel: 1. Open your Voximplant application and go to the **Routing** tab. 2. Select the routing rule that has this scenario attached. 3. Click **Run**. 4. Provide custom data JSON with `destination` and `callerId`. For production, start the routing rule via Management API `startScenarios` and pass the same JSON in `script_custom_data`: [https://voximplant.com/docs/references/httpapi/scenarios#startscenarios](https://voximplant.com/docs/references/httpapi/scenarios#startscenarios) ## Session setup Outbound setup follows this order: 1. Parse `destination` and `callerId` from `VoxEngine.customData()`. 2. Place the PSTN call with `VoxEngine.callPSTN(...)`. 3. Wait for `CallEvents.Connected`. 4. Create `Cartesia.AgentsClient`. 5. Send `start(...)` metadata with `mode: "outbound"`. 6. Bridge call media to the Cartesia client. The scenario deliberately creates and starts the Cartesia session only after the callee answers: ```js title="Create Cartesia client after callee answers" call.addEventListener(CallEvents.Connected, async () => { voiceAIClient = await Cartesia.createAgentsClient({ apiKey: (await ApplicationStorage.get("CARTESIA_API_KEY")).value, cartesiaVersion: "2025-04-16", agentId: (await ApplicationStorage.get("CARTESIA_AGENT_ID")).value }); }); ``` Use outbound mode metadata for this scenario: ```js title="Start outbound session" voiceAIClient.start({ metadata: { channel: "voice", provider: "voximplant", mode: "outbound" } }); ``` Then connect audio between the PSTN leg and Cartesia: ```js title="Bridge media" VoxEngine.sendMediaBetween(call, voiceAIClient); ``` The example also adds: * `CallEvents.Failed` and `CallEvents.Disconnected` handlers for cleanup. * A demo hangup timer to avoid indefinitely running sessions. * Cartesia event logs (`ACK`, `ConnectorInformation`, `Clear`, media started/ended) for debugging. ## Notes [See the VoxEngine API Reference for more details](https://voximplant.com/docs/references/voxengine/cartesia). ## Full VoxEngine scenario ```javascript title={"voxeengine-cartesia-outbound.js"} maxLines={0} /** * Voximplant + Cartesia Line Agents connector demo * Scenario: place an outbound PSTN call and bridge it to Cartesia. */ require(Modules.Cartesia); require(Modules.ApplicationStorage); const MAX_CALL_MS = 2 * 60 * 1000; VoxEngine.addEventListener(AppEvents.Started, async () => { let call; let voiceAIClient; let hangupTimer; try { // This can be provided when manually running a routing rule in the Control Panel, // or via Management API using the `script_custom_data` parameter. // example: {"destination": "+15551234567", "callerId": "+15557654321"} const {destination, callerId} = JSON.parse(VoxEngine.customData()); call = VoxEngine.callPSTN(destination, callerId); call.addEventListener(CallEvents.Failed, () => VoxEngine.terminate()); call.addEventListener(CallEvents.Disconnected, () => { if (hangupTimer) clearTimeout(hangupTimer); VoxEngine.terminate(); }); call.addEventListener(CallEvents.Connected, async () => { hangupTimer = setTimeout(() => { Logger.write("===HANGUP_TIMER==="); call.hangup(); }, MAX_CALL_MS); voiceAIClient = await Cartesia.createAgentsClient({ apiKey: (await ApplicationStorage.get("CARTESIA_API_KEY")).value, cartesiaVersion: "2025-04-16", agentId: (await ApplicationStorage.get("CARTESIA_AGENT_ID")).value, onWebSocketClose: (event) => { Logger.write("===Cartesia.WebSocket.Close==="); if (event) Logger.write(JSON.stringify(event)); VoxEngine.terminate(); }, }); voiceAIClient.start({ metadata: {channel: "voice", provider: "voximplant", mode: "outbound"}, }); // Bridge media between the call and Cartesia Line Agents VoxEngine.sendMediaBetween(call, voiceAIClient); // Consolidated log only handlers for debugging [ Cartesia.AgentsEvents.ACK, Cartesia.AgentsEvents.Clear, Cartesia.AgentsEvents.ConnectorInformation, Cartesia.AgentsEvents.DTMF, Cartesia.AgentsEvents.Unknown, Cartesia.AgentsEvents.WebSocketError, Cartesia.Events.WebSocketMediaStarted, Cartesia.Events.WebSocketMediaEnded, ].forEach((eventName) => { voiceAIClient.addEventListener(eventName, (event) => { Logger.write(`===${event.name}===`); if (event?.data) Logger.write(JSON.stringify(event.data)); }); }); }); } catch (error) { Logger.write("===UNHANDLED_ERROR==="); Logger.write(error); voiceAIClient?.close(); VoxEngine.terminate(); } }); ```