Example: Placing an outbound call

View as Markdown

Overview

This example starts a VoxEngine session, places an outbound PSTN call, and connects the callee to a Grok voice agent when the media WebSocket is ready.

⬇️ Jump to the Full VoxEngine scenario.

Prerequisites

  • A Grok API key stored in ApplicationStorage as XAI_API_KEY.
  • A verified or rented caller ID that can place outbound PSTN calls.
  • Start this scenario via a routing rule (for production use the Management API to start the rule).

Custom data

Pass the destination and caller ID when starting the scenario:

1{"destination":"+16174018889","callerId":"+13322776633"}

Connect call audio

Bridge the PSTN call to Grok once the WebSocket media session is live:

1VoxEngine.sendMediaBetween(call, voiceAgentAPIClient);

Notes

See the VoxEngine API Reference for more details.

Full VoxEngine scenario

voxeengine-grok-place-outbound-call.js
1require(Modules.Grok);
2require(Modules.ApplicationStorage);
3
4const SYSTEM_PROMPT = `
5 Your name is Voxi. You are a helpful voice assistant for phone callers representing the company Voximplant (pronounced VOX-im-plant).
6 Keep responses short and telephony-friendly (usually 1-2 sentences).
7 If the user asks for a live agent or an operator, call the "forward_to_agent" function.
8 If the user says goodbye, call the "hangup_call" function.
9`;
10
11// -------------------- Grok Voice Agent settings --------------------
12const SESSION_PARAMETERS = {
13 session: {
14 voice: "Ara",
15 turn_detection: {type: "server_vad"},
16 instructions: SYSTEM_PROMPT,
17 tools: [
18 {type: "web_search"},
19 {
20 type: "file_search",
21 vector_store_ids: ["collection_4c5a63ab-f739-4c13-93d2-05b74095c34a"], // optional RAG
22 max_num_results: 5,
23 },
24 {
25 type: "x_search",
26 allowed_x_handles: ["voximplant", "aylarov"],
27 },
28 {
29 type: "function",
30 name: "forward_to_agent",
31 description: "Forward the user to a live agent",
32 parameters: {
33 type: "object",
34 properties: {},
35 required: [],
36 },
37 },
38 {
39 type: "function",
40 name: "hangup_call",
41 description: "Hangup the call",
42 parameters: {
43 type: "object",
44 properties: {},
45 required: [],
46 },
47 },
48 ],
49 },
50};
51
52// Helper to parse custom data JSON from call setup
53function parseCustomData() {
54 const raw = VoxEngine.customData();
55 if (!raw) return {};
56 try {
57 return JSON.parse(raw);
58 } catch (error) {
59 Logger.write("===CUSTOM_DATA_PARSE_FAILED===");
60 Logger.write(error);
61 return {};
62 }
63}
64
65VoxEngine.addEventListener(AppEvents.Started, async () => {
66 let voiceAIClient;
67 let call;
68 let hangupCall = false;
69 let forwardToLiveAgent = false;
70
71 const {destination, callerId} = parseCustomData();
72
73 if (!destination || !callerId) {
74 Logger.write(
75 "Missing destination or callerId. Provide custom data: {\"destination\":\"+16174018889\",\"callerId\":\"+13322776633\"}",
76 );
77 VoxEngine.terminate();
78 return;
79 }
80
81 call = VoxEngine.callPSTN(destination, callerId);
82 // Alternative outbound paths (uncomment to use):
83 // call = VoxEngine.callUser({username: destination, callerid: callerId});
84 // call = VoxEngine.callSIP(`sip:${destination}@your-sip-domain`, callerId);
85 // call = VoxEngine.callWhatsappUser({number: destination, callerid: callerId});
86
87 // Termination functions - add cleanup and logging as needed
88 call.addEventListener(CallEvents.Disconnected, ()=>VoxEngine.terminate());
89 call.addEventListener(CallEvents.Failed, ()=>VoxEngine.terminate());
90
91 try {
92 voiceAIClient = await Grok.createVoiceAgentAPIClient({
93 xAIApiKey: (await ApplicationStorage.get("XAI_API_KEY")).value,
94 onWebSocketClose: (event) => {
95 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
96 VoxEngine.terminate();
97 },
98 });
99
100 voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.ConversationCreated, (event) => {
101 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
102 voiceAIClient.sessionUpdate(SESSION_PARAMETERS);
103 });
104
105 voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.SessionUpdated, (event) => {
106 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
107 voiceAIClient.responseCreate({instructions: "Hello."});
108 });
109
110 // Keep it interruption-friendly (barge-in).
111 voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.InputAudioBufferSpeechStarted, (event) => {
112 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
113 voiceAIClient.clearMediaBuffer();
114 });
115
116 // Function calling.
117 voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.ResponseFunctionCallArgumentsDone, (event) => {
118 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
119
120 const {name, call_id} = event?.data?.payload || {};
121 let output;
122
123 if (name !== "forward_to_agent" && name !== "hangup_call") {
124 Logger.write(`===Ignoring unhandled function call: ${name}===`);
125 return;
126 }
127
128 if (name === "forward_to_agent") {
129 forwardToLiveAgent = true;
130 output = {result: "Forwarding your call to a live agent. Please hold on."};
131 } else if (name === "hangup_call") {
132 hangupCall = true;
133 output = {result: "Have a great day, goodbye!"};
134 }
135
136 voiceAIClient.conversationItemCreate({
137 item: {
138 type: "function_call_output",
139 call_id,
140 output: JSON.stringify(output),
141 },
142 });
143 voiceAIClient.responseCreate({});
144 });
145
146 // -------------------- Log Other Events --------------------
147 [
148 CallEvents.FirstAudioPacketReceived,
149 Grok.Events.WebSocketMediaStarted,
150 Grok.VoiceAgentAPIEvents.InputAudioBufferSpeechStopped,
151 Grok.VoiceAgentAPIEvents.ConversationItemInputAudioTranscriptionCompleted,
152 Grok.VoiceAgentAPIEvents.ConversationItemAdded,
153 Grok.VoiceAgentAPIEvents.ResponseCreated,
154 Grok.VoiceAgentAPIEvents.ResponseOutputItemAdded,
155 Grok.VoiceAgentAPIEvents.ResponseDone,
156 Grok.VoiceAgentAPIEvents.ResponseOutputAudioTranscriptDelta,
157 Grok.VoiceAgentAPIEvents.ResponseOutputAudioTranscriptDone,
158 Grok.VoiceAgentAPIEvents.ResponseOutputAudioDelta,
159 Grok.VoiceAgentAPIEvents.ResponseOutputAudioDone,
160 Grok.VoiceAgentAPIEvents.ResponseOutputItemDone,
161 Grok.VoiceAgentAPIEvents.ConnectorInformation,
162 Grok.VoiceAgentAPIEvents.InputAudioBufferCommitted,
163 Grok.VoiceAgentAPIEvents.WebSocketError,
164 Grok.VoiceAgentAPIEvents.Unknown,
165 ].forEach((evtName) => {
166 voiceAIClient.addEventListener(evtName, (e) => {
167 Logger.write(`===${e.name}===>${JSON.stringify(e)}`);
168 });
169 });
170
171 voiceAIClient.addEventListener(Grok.Events.WebSocketMediaStarted, () => {
172 VoxEngine.sendMediaBetween(call, voiceAIClient);
173 });
174
175 voiceAIClient.addEventListener(Grok.Events.WebSocketMediaEnded, (event) => {
176 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
177 if (hangupCall) callCloseHandler();
178 else if (forwardToLiveAgent) {
179 call.say("Here is where I would forward the call via the phone network or SIP.");
180 call.addEventListener(CallEvents.PlaybackFinished, callCloseHandler);
181 } else return;
182 });
183 } catch (error) {
184 Logger.write("===SOMETHING_WENT_WRONG===");
185 Logger.write(error);
186 VoxEngine.terminate();
187 }
188});