Skip to navigation

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 Qwen Cloud after they answer.

⬇️ Jump to the Full VoxEngine scenario.

Prerequisites

  • A Qwen Cloud API key stored in Voximplant Secrets under QWEN_API_KEY.
  • The example uses qwen-audio-3.1-realtime-plus. Change QWEN_MODEL if your account should use another realtime model.
  • A verified or rented caller ID that can place outbound PSTN calls.
  • Start this scenario via a routing rule. For production, start the rule with the Management API.

Custom data

Pass the destination and caller ID when starting the scenario:

{"destination":"+15551234567","callerId":"+13322776633"}

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.
  • Voximplant users (VoxEngine.callUser): call another app user in the same Voximplant application, such as a Web SDK, mobile SDK, or SIP user.

Relevant guides:

Connect call audio

Create the Qwen client inside CallEvents.Connected. After SessionUpdated, bridge the call:

VoxEngine.sendMediaBetween(call, voiceAIClient);
Media bridge

QwenCloud.VoiceAgentAPIClient is not in VoxMediaUnitMap yet. sendMediaBetween matches the other realtime connectors. See Overview for the typings gap.

Notes

See the VoxEngine API Reference for more details.

Full VoxEngine scenario

voxeengine-qwen-place-outbound-call.js
require(Modules.QwenCloud);
const QWEN_MODEL = "qwen-audio-3.1-realtime-plus";
const SYSTEM_PROMPT = `
You are Voxi, a concise phone agent for Voximplant callers.
Voximplant is pronounced VOX-im-plant.
Reply in English. Keep answers brief and helpful.
If you do not know, say so and offer to connect them to a human.
`;
const SESSION_PARAMETERS = {
session: {
modalities: ["text", "audio"],
voice: "beth_v3.1",
instructions: SYSTEM_PROMPT,
input_audio_transcription: {language: "en"},
output_audio: {language: "en"},
turn_detection: {type: "server_vad"},
},
};
function parseCustomData() {
const raw = VoxEngine.customData();
if (!raw) return {};
try {
return JSON.parse(raw);
} catch (error) {
Logger.write("===CUSTOM_DATA_PARSE_FAILED===");
Logger.write(error);
return {};
}
}
VoxEngine.addEventListener(AppEvents.Started, () => {
let voiceAIClient;
const {destination, callerId} = parseCustomData();
if (!destination || !callerId) {
Logger.write(
"Missing destination or callerId. Provide custom data: {\"destination\":\"+15551234567\",\"callerId\":\"+13322776633\"}",
);
VoxEngine.terminate();
return;
}
const call = VoxEngine.callPSTN(destination, callerId);
// Alternative outbound paths (uncomment one and comment out callPSTN):
// const call = VoxEngine.callUser({username: destination, callerid: callerId});
// const call = VoxEngine.callSIP(`sip:${destination}@your-sip-domain`, callerId);
// const call = VoxEngine.callWhatsappUser({number: destination, callerid: callerId});
const terminate = (event) => {
if (event) Logger.write(JSON.stringify(event));
voiceAIClient?.close();
VoxEngine.terminate();
};
call.addEventListener(CallEvents.Disconnected, terminate);
call.addEventListener(CallEvents.Failed, terminate);
call.addEventListener(CallEvents.Connected, async () => {
try {
const providerApiKey = VoxEngine.getSecretValue("QWEN_API_KEY");
if (!providerApiKey) throw new Error("Set QWEN_API_KEY in Voximplant Secrets");
voiceAIClient = await QwenCloud.createVoiceAgentAPIClient({
apiKey: providerApiKey,
model: QWEN_MODEL,
onWebSocketClose: terminate,
});
voiceAIClient.addEventListener(QwenCloud.VoiceAgentAPIEvents.SessionCreated, (event) => {
Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
voiceAIClient.sessionUpdate(SESSION_PARAMETERS);
});
voiceAIClient.addEventListener(QwenCloud.VoiceAgentAPIEvents.SessionUpdated, (event) => {
Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
VoxEngine.sendMediaBetween(call, voiceAIClient);
voiceAIClient.conversationItemCreate({
item: {
type: "message",
role: "user",
content: [{
type: "input_text",
text: "The call just connected. Greet the caller in one short sentence.",
}],
},
});
voiceAIClient.responseCreate({});
});
voiceAIClient.addEventListener(
QwenCloud.VoiceAgentAPIEvents.InputAudioBufferSpeechStarted,
(event) => {
Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
voiceAIClient.clearMediaBuffer();
},
);
[
QwenCloud.Events.WebSocketMediaStarted,
QwenCloud.Events.WebSocketMediaEnded,
QwenCloud.VoiceAgentAPIEvents.ConnectorInformation,
QwenCloud.VoiceAgentAPIEvents.ResponseCreated,
QwenCloud.VoiceAgentAPIEvents.ResponseDone,
QwenCloud.VoiceAgentAPIEvents.ResponseAudioTranscriptDone,
QwenCloud.VoiceAgentAPIEvents.ConversationItemInputAudioTranscriptionCompleted,
QwenCloud.VoiceAgentAPIEvents.Error,
QwenCloud.VoiceAgentAPIEvents.WebSocketError,
QwenCloud.VoiceAgentAPIEvents.Unknown,
].forEach((evtName) => {
voiceAIClient.addEventListener(evtName, (e) => {
Logger.write(`===${e.name}===>${JSON.stringify(e)}`);
});
});
} catch (error) {
Logger.write("===SOMETHING_WENT_WRONG===");
Logger.write(JSON.stringify(error));
VoxEngine.terminate();
}
});
});