Example: Placing an outbound call

View as Markdown

For the complete documentation index, see llms.txt.

Overview

This example starts a VoxEngine session, places an outbound PSTN call, and connects the callee to Gemini Live API when the session is ready.

⬇️ Jump to the Full VoxEngine scenario.

Gemini 3.1 Flash Live Preview

This page reflects the current gemini-3.1-flash-live-preview flow from Google’s Live API docs: https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-live-preview

Prerequisites

About outbound Caller ID

VoxEngine.callPSTN(...) requires a valid callback-capable caller ID (for example, a rented Voximplant number or a verified caller ID). See https://voximplant.com/docs/getting-started/basic-concepts/phone-numbers.

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 via Management API startScenarios (pass rule_id, and pass the same JSON string in script_custom_data): https://voximplant.com/docs/references/httpapi/scenarios#startscenarios

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:

Notes

  • The example uses the Gemini Developer API (Gemini.Backend.GEMINI_API), not Vertex AI.
  • The current sample uses gemini-3.1-flash-live-preview.
  • Audio is connected after Gemini.LiveAPIEvents.SetupComplete fires.
Gemini 2.5 compatibility

If you are adapting an older 2.5 outbound sample, use sendRealtimeInput(...) for the initial prompt on 3.1. The current 3.1 sample also uses thinkingConfig: { thinkingLevel: "minimal" }.

See the VoxEngine API Reference for more details.

Full VoxEngine scenario

voxeengine-gemini-place-outbound-call.js
1/**
2 * Voximplant + Gemini Live API connector demo
3 * Scenario: place an outbound PSTN call and bridge it to Gemini Live API.
4 */
5
6require(Modules.Gemini);
7const SYSTEM_PROMPT = `
8You are Voxi, a helpful voice assistant for phone callers.
9Keep responses short and telephony-friendly (usually 1-2 sentences).
10`;
11
12// -------------------- Gemini Live API settings --------------------
13const CONNECT_CONFIG = {
14 responseModalities: ["AUDIO"],
15 thinkingConfig: {thinkingLevel: "minimal"},
16 systemInstruction: {
17 parts: [{text: SYSTEM_PROMPT}],
18 },
19 inputAudioTranscription: {},
20 outputAudioTranscription: {},
21};
22
23VoxEngine.addEventListener(AppEvents.Started, async () => {
24 let voiceAIClient;
25 let call;
26
27 try {
28 // This can be provided when manually running a routing rule in the Control Panel,
29 // or via Management API using the `script_custom_data` parameter.
30 // example: {"destination": "+15551234567","callerId": "+15557654321"}
31 const {destination, callerId} = JSON.parse(VoxEngine.customData());
32
33 // Place the outbound call
34 call = VoxEngine.callPSTN(destination, callerId);
35 // Alternative outbound paths (uncomment to use):
36 // call = VoxEngine.callUser({username: destination, callerid: callerId});
37 // call = VoxEngine.callSIP(`sip:${destination}@your-sip-domain`, callerId);
38 // call = VoxEngine.callWhatsappUser({number: destination, callerid: callerId});
39
40 // Termination functions - add cleanup and logging as needed
41 call.addEventListener(CallEvents.Disconnected, VoxEngine.terminate);
42 call.addEventListener(CallEvents.Failed, VoxEngine.terminate);
43
44 call.addEventListener(CallEvents.Connected, async () => {
45 // call.record({ hd_audio: true, stereo: true }); // Optional: record the call
46
47 voiceAIClient = await Gemini.createLiveAPIClient({
48 apiKey: VoxEngine.getSecretValue('GEMINI_API_KEY'),
49 model: "gemini-3.1-flash-live-preview",
50 backend: Gemini.Backend.GEMINI_API,
51 connectConfig: CONNECT_CONFIG,
52 onWebSocketClose: () => {
53 Logger.write(`===Gemini.WebSocket.Close===`);
54 VoxEngine.terminate();
55 },
56 });
57
58 voiceAIClient.addEventListener(Gemini.LiveAPIEvents.SetupComplete, () => {
59 VoxEngine.sendMediaBetween(call, voiceAIClient);
60 voiceAIClient.sendRealtimeInput({
61 text: "Say hello and ask how you can help.",
62 });
63 });
64
65 // Capture transcripts + handle barge-in
66 voiceAIClient.addEventListener(Gemini.LiveAPIEvents.ServerContent, (event) => {
67 const payload = event?.data?.payload || {};
68 if (payload.inputTranscription?.text) {
69 Logger.write(`===USER=== ${payload.inputTranscription.text}`);
70 }
71 if (payload.outputTranscription?.text) {
72 Logger.write(`===AGENT=== ${payload.outputTranscription.text}`);
73 }
74 if (payload.interrupted) {
75 Logger.write("===BARGE-IN=== Gemini.LiveAPIEvents.ServerContent");
76 voiceAIClient.clearMediaBuffer();
77 }
78 });
79
80 // Log all Gemini events for illustration/debugging
81 [
82 Gemini.LiveAPIEvents.SetupComplete,
83 Gemini.LiveAPIEvents.ServerContent,
84 Gemini.LiveAPIEvents.ToolCall,
85 Gemini.LiveAPIEvents.ToolCallCancellation,
86 Gemini.LiveAPIEvents.ConnectorInformation,
87 Gemini.LiveAPIEvents.Unknown,
88 Gemini.Events.WebSocketMediaStarted,
89 Gemini.Events.WebSocketMediaEnded,
90 ].forEach((eventName) => {
91 voiceAIClient.addEventListener(eventName, (event) => {
92 Logger.write(`===${event.name}===`);
93 if (event?.data) Logger.write(JSON.stringify(event.data));
94 });
95 });
96 });
97 } catch (error) {
98 Logger.write("===SOMETHING_WENT_WRONG===");
99 Logger.write(error);
100 voiceAIClient?.close();
101 VoxEngine.terminate();
102 }
103});