Example: Placing an outbound call
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
ApplicationStorageasXAI_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:
1 VoxEngine.sendMediaBetween(call, voiceAgentAPIClient);
Notes
See the VoxEngine API Reference for more details.
Full VoxEngine scenario
voxeengine-grok-place-outbound-call.js
1 require(Modules.Grok); 2 require(Modules.ApplicationStorage); 3 4 const 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 -------------------- 12 const 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 53 function 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 65 VoxEngine.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 });