Example: Placing an outbound call

View as MarkdownOpen in Claude

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.

Prerequisites

  • Create a Cartesia Line Agent in the Cartesia Voice Agents portal
  • Store your Cartesia API key in Voximplant ApplicationStorage under CARTESIA_API_KEY
  • Store your Cartesia Agent ID 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:

Custom data example
1{"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

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:

Create Cartesia client after callee answers
1call.addEventListener(CallEvents.Connected, async () => {
2 voiceAIClient = await Cartesia.createAgentsClient({
3 apiKey: (await ApplicationStorage.get("CARTESIA_API_KEY")).value,
4 cartesiaVersion: "2025-04-16",
5 agentId: (await ApplicationStorage.get("CARTESIA_AGENT_ID")).value
6 });
7});

Use outbound mode metadata for this scenario:

Start outbound session
1voiceAIClient.start({
2 metadata: {
3 channel: "voice",
4 provider: "voximplant",
5 mode: "outbound"
6 }
7});

Then connect audio between the PSTN leg and Cartesia:

Bridge media
1VoxEngine.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.

Full VoxEngine scenario

voxeengine-cartesia-outbound.js
1/**
2 * Voximplant + Cartesia Line Agents connector demo
3 * Scenario: place an outbound PSTN call and bridge it to Cartesia.
4 */
5
6require(Modules.Cartesia);
7require(Modules.ApplicationStorage);
8
9const MAX_CALL_MS = 2 * 60 * 1000;
10
11VoxEngine.addEventListener(AppEvents.Started, async () => {
12 let call;
13 let voiceAIClient;
14 let hangupTimer;
15
16 try {
17 // This can be provided when manually running a routing rule in the Control Panel,
18 // or via Management API using the `script_custom_data` parameter.
19 // example: {"destination": "+15551234567", "callerId": "+15557654321"}
20 const {destination, callerId} = JSON.parse(VoxEngine.customData());
21
22 call = VoxEngine.callPSTN(destination, callerId);
23
24 call.addEventListener(CallEvents.Failed, () => VoxEngine.terminate());
25 call.addEventListener(CallEvents.Disconnected, () => {
26 if (hangupTimer) clearTimeout(hangupTimer);
27 VoxEngine.terminate();
28 });
29
30 call.addEventListener(CallEvents.Connected, async () => {
31 hangupTimer = setTimeout(() => {
32 Logger.write("===HANGUP_TIMER===");
33 call.hangup();
34 }, MAX_CALL_MS);
35
36 voiceAIClient = await Cartesia.createAgentsClient({
37 apiKey: (await ApplicationStorage.get("CARTESIA_API_KEY")).value,
38 cartesiaVersion: "2025-04-16",
39 agentId: (await ApplicationStorage.get("CARTESIA_AGENT_ID")).value,
40 onWebSocketClose: (event) => {
41 Logger.write("===Cartesia.WebSocket.Close===");
42 if (event) Logger.write(JSON.stringify(event));
43 VoxEngine.terminate();
44 },
45 });
46
47 voiceAIClient.start({
48 metadata: {channel: "voice", provider: "voximplant", mode: "outbound"},
49 });
50
51 // Bridge media between the call and Cartesia Line Agents
52 VoxEngine.sendMediaBetween(call, voiceAIClient);
53
54 // Consolidated log only handlers for debugging
55 [
56 Cartesia.AgentsEvents.ACK,
57 Cartesia.AgentsEvents.Clear,
58 Cartesia.AgentsEvents.ConnectorInformation,
59 Cartesia.AgentsEvents.DTMF,
60 Cartesia.AgentsEvents.Unknown,
61 Cartesia.AgentsEvents.WebSocketError,
62 Cartesia.Events.WebSocketMediaStarted,
63 Cartesia.Events.WebSocketMediaEnded,
64 ].forEach((eventName) => {
65 voiceAIClient.addEventListener(eventName, (event) => {
66 Logger.write(`===${event.name}===`);
67 if (event?.data) Logger.write(JSON.stringify(event.data));
68 });
69 });
70 });
71 } catch (error) {
72 Logger.write("===UNHANDLED_ERROR===");
73 Logger.write(error);
74 voiceAIClient?.close();
75 VoxEngine.terminate();
76 }
77});