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 83 // Termination functions - add cleanup and logging as needed 84 call.addEventListener(CallEvents.Disconnected, ()=>VoxEngine.terminate()); 85 call.addEventListener(CallEvents.Failed, ()=>VoxEngine.terminate()); 86 87 try { 88 voiceAIClient = await Grok.createVoiceAgentAPIClient({ 89 xAIApiKey: (await ApplicationStorage.get("XAI_API_KEY")).value, 90 onWebSocketClose: (event) => { 91 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`); 92 VoxEngine.terminate(); 93 }, 94 }); 95 96 voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.ConversationCreated, (event) => { 97 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`); 98 voiceAIClient.sessionUpdate(SESSION_PARAMETERS); 99 }); 100 101 voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.SessionUpdated, (event) => { 102 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`); 103 voiceAIClient.responseCreate({instructions: "Hello."}); 104 }); 105 106 // Keep it interruption-friendly (barge-in). 107 voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.InputAudioBufferSpeechStarted, (event) => { 108 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`); 109 voiceAIClient.clearMediaBuffer(); 110 }); 111 112 // Function calling. 113 voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.ResponseFunctionCallArgumentsDone, (event) => { 114 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`); 115 116 const {name, call_id} = event?.data?.payload || {}; 117 let output; 118 119 if (name !== "forward_to_agent" && name !== "hangup_call") { 120 Logger.write(`===Ignoring unhandled function call: ${name}===`); 121 return; 122 } 123 124 if (name === "forward_to_agent") { 125 forwardToLiveAgent = true; 126 output = {result: "Forwarding your call to a live agent. Please hold on."}; 127 } else if (name === "hangup_call") { 128 hangupCall = true; 129 output = {result: "Have a great day, goodbye!"}; 130 } 131 132 voiceAIClient.conversationItemCreate({ 133 item: { 134 type: "function_call_output", 135 call_id, 136 output: JSON.stringify(output), 137 }, 138 }); 139 voiceAIClient.responseCreate({}); 140 }); 141 142 // -------------------- Log Other Events -------------------- 143 [ 144 CallEvents.FirstAudioPacketReceived, 145 Grok.Events.WebSocketMediaStarted, 146 Grok.VoiceAgentAPIEvents.InputAudioBufferSpeechStopped, 147 Grok.VoiceAgentAPIEvents.ConversationItemInputAudioTranscriptionCompleted, 148 Grok.VoiceAgentAPIEvents.ConversationItemAdded, 149 Grok.VoiceAgentAPIEvents.ResponseCreated, 150 Grok.VoiceAgentAPIEvents.ResponseOutputItemAdded, 151 Grok.VoiceAgentAPIEvents.ResponseDone, 152 Grok.VoiceAgentAPIEvents.ResponseOutputAudioTranscriptDelta, 153 Grok.VoiceAgentAPIEvents.ResponseOutputAudioTranscriptDone, 154 Grok.VoiceAgentAPIEvents.ResponseOutputAudioDelta, 155 Grok.VoiceAgentAPIEvents.ResponseOutputAudioDone, 156 Grok.VoiceAgentAPIEvents.ResponseOutputItemDone, 157 Grok.VoiceAgentAPIEvents.ConnectorInformation, 158 Grok.VoiceAgentAPIEvents.InputAudioBufferCommitted, 159 Grok.VoiceAgentAPIEvents.WebSocketError, 160 Grok.VoiceAgentAPIEvents.Unknown, 161 ].forEach((evtName) => { 162 voiceAIClient.addEventListener(evtName, (e) => { 163 Logger.write(`===${e.name}===>${JSON.stringify(e)}`); 164 }); 165 }); 166 167 voiceAIClient.addEventListener(Grok.Events.WebSocketMediaStarted, () => { 168 VoxEngine.sendMediaBetween(call, voiceAIClient); 169 }); 170 171 voiceAIClient.addEventListener(Grok.Events.WebSocketMediaEnded, (event) => { 172 Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`); 173 if (hangupCall) callCloseHandler(); 174 else if (forwardToLiveAgent) { 175 call.say("Here is where I would forward the call via the phone network or SIP."); 176 call.addEventListener(CallEvents.PlaybackFinished, callCloseHandler); 177 } else return; 178 }); 179 } catch (error) { 180 Logger.write("===SOMETHING_WENT_WRONG==="); 181 Logger.write(error); 182 VoxEngine.terminate(); 183 } 184 });