*** ## title: 'Example: Placing an outbound call' This example starts a VoxEngine session, places an outbound PSTN call, and bridges audio to ElevenLabs Agents once the callee answers. **Jump to the [Full VoxEngine scenario](#full-voxengine-scenario).** ## Prerequisites * Store your ElevenLabs API key in Voximplant `ApplicationStorage` under `ELEVENLABS_API_KEY`. * Store your ElevenLabs Agent ID in Voximplant `ApplicationStorage` under `ELEVENLABS_AGENT_ID`. * Ensure outbound calling is enabled for your Voximplant application and that your caller ID is verified. ## Outbound call parameters The example expects destination and caller ID in `customData` (read via `VoxEngine.customData()`): ```json title="Custom data example" {"destination":"+15551234567","callerId":"+15557654321"} ``` ## Launch the routing rule For quick testing, you can start this outbound scenario 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** (max 200 bytes) with `destination` and `callerId`: ```json title="Custom data example" {"destination":"+15551234567","callerId":"+15557654321"} ``` For production, start the routing rule via Management API `startScenarios` (pass `rule_id`, and pass the same JSON string in `script_custom_data`): [https://voximplant.com/docs/references/httpapi/scenarios#startscenarios](https://voximplant.com/docs/references/httpapi/scenarios#startscenarios) ## Connect call audio After the callee answers, the example creates an `ElevenLabs.AgentsClient` and bridges audio: ```js title="Connect call audio" VoxEngine.sendMediaBetween(call, agentsClient); ``` ## Barge-in ```js title="Barge-in" agentsClient.addEventListener(ElevenLabs.AgentsEvents.Interruption, () => { agentsClient.clearMediaBuffer(); }); ``` ## Notes [See the VoxEngine API Reference for more details](https://voximplant.com/docs/references/voxengine/elevenlabs). ## Full VoxEngine scenario ```javascript title={"voxeengine-elevenlabs-outbound.js"} maxLines={0} /** * Voximplant + ElevenLabs Agents connector demo * Scenario: place an outbound PSTN call and bridge it to ElevenLabs Agents. */ require(Modules.ElevenLabs); require(Modules.ApplicationStorage); const MAX_CALL_MS = 2 * 60 * 1000; // Maximum call duration of 2 minutes 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); // Create client and connect to ElevenLabs Agents voiceAIClient = await ElevenLabs.createAgentsClient({ xiApiKey: (await ApplicationStorage.get("ELEVENLABS_API_KEY")).value, agentId: (await ApplicationStorage.get("ELEVENLABS_AGENT_ID")).value, onWebSocketClose: (event) => { Logger.write("===ElevenLabs.WebSocket.Close==="); if (event) Logger.write(JSON.stringify(event)); VoxEngine.terminate(); }, }); // Bridge media between the call and ElevenLabs Agents VoxEngine.sendMediaBetween(call, voiceAIClient); // ---------------------- Event handlers ----------------------- // Barge-in: keep conversation responsive voiceAIClient.addEventListener(ElevenLabs.AgentsEvents.Interruption, () => { Logger.write("===BARGE-IN: ElevenLabs.AgentsEvents.Interruption==="); voiceAIClient.clearMediaBuffer(); }); voiceAIClient.addEventListener(ElevenLabs.AgentsEvents.UserTranscript, (event) => { const payload = event?.data?.payload || event?.data || {}; const text = payload.text || payload.transcript || payload.user_transcript; if (text) { Logger.write(`===USER=== ${text}`); } else { Logger.write("===USER_TRANSCRIPT==="); Logger.write(JSON.stringify(payload)); } }); voiceAIClient.addEventListener(ElevenLabs.AgentsEvents.AgentResponse, (event) => { const payload = event?.data?.payload || event?.data || {}; const text = payload.text || payload.response || payload.agent_response; if (text) { Logger.write(`===AGENT=== ${text}`); } else { Logger.write("===AGENT_RESPONSE==="); Logger.write(JSON.stringify(payload)); } }); // Consolidated "log-only" handlers - key ElevenLabs/VoxEngine debugging events [ ElevenLabs.AgentsEvents.ConversationInitiationMetadata, ElevenLabs.AgentsEvents.AgentResponseCorrection, ElevenLabs.AgentsEvents.ContextualUpdate, ElevenLabs.AgentsEvents.AgentToolResponse, ElevenLabs.AgentsEvents.VadScore, ElevenLabs.AgentsEvents.Ping, ElevenLabs.AgentsEvents.HTTPResponse, ElevenLabs.AgentsEvents.WebSocketError, ElevenLabs.AgentsEvents.ConnectorInformation, ElevenLabs.AgentsEvents.Unknown, ElevenLabs.Events.WebSocketMediaStarted, ElevenLabs.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(); } }); ```