Example: Placing an outbound call

View as Markdown

For the complete documentation index, see llms.txt.

This example starts a VoxEngine session, places an outbound PSTN call, and bridges audio to OpenAI Realtime once the callee answers. Select a model version to keep the session configuration and complete scenario synchronized. gpt-realtime-2.1 is the default for new applications.

Jump to the Full VoxEngine scenarios.

Prerequisites

  • Store your OpenAI API key in Voximplant Secrets under OPENAI_API_KEY.
  • 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()):

Custom data example
1{"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:
Custom data example
1{"destination":"+15551234567","callerId":"+15557654321"}

For production, start the routing rule with the Management API startScenarios method. Pass rule_id and the same JSON string in script_custom_data.

Alternate outbound destinations

This example uses VoxEngine.callPSTN(...) for PSTN dialing. You can also route outbound calls to other destination types in VoxEngine:

  • SIP (VoxEngine.callSIP): dial a SIP URI to reach a PBX, carrier, SIP trunk, or other SIP endpoint.
  • WhatsApp (VoxEngine.callWhatsappUser): place a WhatsApp Business-initiated call (requires a WhatsApp Business account and enabled numbers).
  • Voximplant users (VoxEngine.callUser): calls another app user inside the same Voximplant application such as web SDK, mobile SDK, or SIP user.

Relevant guides:

Session setup

The outbound call setup is shared between versions. Only the Realtime model and session configuration change.

The example configures the OpenAI Realtime session with a short system prompt, enables semantic VAD, and uses low reasoning effort.

GPT Realtime 2.1 session setup
1voiceAIClient.sessionUpdate({
2 session: {
3 type: "realtime",
4 model: "gpt-realtime-2.1",
5 instructions: SYSTEM_PROMPT,
6 reasoning: { effort: "low" },
7 output_modalities: ["audio"],
8 audio: {
9 input: {
10 turn_detection: { type: "semantic_vad", interrupt_response: true },
11 },
12 output: {
13 voice: "alloy",
14 },
15 },
16 },
17});

Connect call audio

After the callee answers, the example bridges audio both ways:

Connect call audio
1VoxEngine.sendMediaBetween(call, voiceAIClient);

Barge-in

The scenario clears buffered model audio whenever OpenAI detects caller speech:

Barge-in
1voiceAIClient.addEventListener(OpenAI.RealtimeAPIEvents.InputAudioBufferSpeechStarted, () => {
2 voiceAIClient.clearMediaBuffer();
3});

Full VoxEngine scenarios

voxeengine-openai-place-outbound-call.js
1/**
2 * Voximplant + OpenAI Realtime API connector demo
3 * Scenario: place an outbound PSTN call and bridge it to OpenAI Realtime 2.1.
4 */
5
6require(Modules.OpenAI);
7
8const SYSTEM_PROMPT = `
9You are Voxi, a concise phone assistant for outbound calls.
10Keep responses short and helpful.
11`;
12
13const SESSION_CONFIG = {
14 session: {
15 type: "realtime",
16 model: "gpt-realtime-2.1",
17 instructions: SYSTEM_PROMPT,
18 reasoning: {effort: "low"},
19 output_modalities: ["audio"],
20 audio: {
21 input: {
22 turn_detection: {type: "semantic_vad", interrupt_response: true},
23 },
24 output: {
25 voice: "alloy",
26 },
27 },
28 },
29};
30
31const MAX_CALL_MS = 2 * 60 * 1000;
32
33VoxEngine.addEventListener(AppEvents.Started, async () => {
34 let call;
35 let voiceAIClient;
36 let hangupTimer;
37
38 try {
39 // Custom data example: {"destination":"+15551234567","callerId":"+15557654321"}
40 const {destination, callerId} = JSON.parse(VoxEngine.customData());
41
42 call = VoxEngine.callPSTN(destination, callerId);
43 // Alternative outbound paths (uncomment to use):
44 // call = VoxEngine.callUser({username: destination, callerid: callerId});
45 // call = VoxEngine.callSIP(`sip:${destination}@your-sip-domain`, callerId);
46 // call = VoxEngine.callWhatsappUser({number: destination, callerid: callerId});
47
48 call.addEventListener(CallEvents.Failed, () => VoxEngine.terminate());
49 call.addEventListener(CallEvents.Disconnected, () => {
50 if (hangupTimer) clearTimeout(hangupTimer);
51 VoxEngine.terminate();
52 });
53
54 call.addEventListener(CallEvents.Connected, async () => {
55 hangupTimer = setTimeout(() => {
56 Logger.write("===HANGUP_TIMER===");
57 call.hangup();
58 }, MAX_CALL_MS);
59
60 voiceAIClient = await OpenAI.createRealtimeAPIClient({
61 apiKey: VoxEngine.getSecretValue("OPENAI_API_KEY"),
62 model: "gpt-realtime-2.1",
63 onWebSocketClose: (event) => {
64 Logger.write("===OpenAI.WebSocket.Close===");
65 if (event) Logger.write(JSON.stringify(event));
66 VoxEngine.terminate();
67 },
68 });
69
70 voiceAIClient.addEventListener(OpenAI.RealtimeAPIEvents.SessionCreated, () => {
71 voiceAIClient.sessionUpdate(SESSION_CONFIG);
72 });
73
74 voiceAIClient.addEventListener(OpenAI.RealtimeAPIEvents.SessionUpdated, () => {
75 VoxEngine.sendMediaBetween(call, voiceAIClient);
76 voiceAIClient.conversationItemCreate({
77 item: {
78 type: "message",
79 role: "user",
80 content: [
81 {
82 type: "input_text",
83 text: "The phone call just connected. "
84 + 'Say exactly: "Hello! This is Voxi. How can I help today?"',
85 },
86 ],
87 },
88 });
89 voiceAIClient.responseCreate({});
90 });
91
92 voiceAIClient.addEventListener(
93 OpenAI.RealtimeAPIEvents.InputAudioBufferSpeechStarted,
94 () => {
95 Logger.write("===BARGE-IN: OpenAI.InputAudioBufferSpeechStarted===");
96 voiceAIClient.clearMediaBuffer();
97 },
98 );
99
100 // Consolidated "log-only" handlers
101 [
102 OpenAI.RealtimeAPIEvents.ResponseCreated,
103 OpenAI.RealtimeAPIEvents.ResponseDone,
104 OpenAI.RealtimeAPIEvents.ResponseOutputAudioDone,
105 OpenAI.RealtimeAPIEvents.ConversationItemInputAudioTranscriptionCompleted,
106 OpenAI.RealtimeAPIEvents.ResponseOutputAudioTranscriptDone,
107 OpenAI.RealtimeAPIEvents.ConnectorInformation,
108 OpenAI.RealtimeAPIEvents.HTTPResponse,
109 OpenAI.RealtimeAPIEvents.WebSocketError,
110 OpenAI.RealtimeAPIEvents.Unknown,
111 OpenAI.Events.WebSocketMediaStarted,
112 OpenAI.Events.WebSocketMediaEnded,
113 ].forEach((eventName) => {
114 voiceAIClient.addEventListener(eventName, (event) => {
115 Logger.write(`===${event.name}===`);
116 if (event?.data) Logger.write(JSON.stringify(event.data));
117 });
118 });
119 });
120 } catch (error) {
121 Logger.write("===UNHANDLED_ERROR===");
122 Logger.write(error);
123 voiceAIClient?.close();
124 VoxEngine.terminate();
125 }
126});

More information