> For a complete documentation index, fetch https://docs.voximplant.ai/llms.txt

# Example: Placing an outbound call

> 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.

<blockquote>
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

## 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](#full-voxengine-scenario).**

## Prerequisites

* A Grok API key stored in Voximplant [Secrets](/platform/voxengine/secrets) under `GROK_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:

```json
{"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 (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:

* [SIP calling options](/getting-started/network-options/sip)
* [Voximplant users calling options](/getting-started/network-options/web-mobile)
* [WhatsApp calling options](/getting-started/network-options/whatsapp)

## Connect call audio

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

```js
VoxEngine.sendMediaBetween(call, voiceAgentAPIClient);
```

## Notes

[See the VoxEngine API Reference for more details](https://voximplant.com/docs/references/voxengine/grok).

## Full VoxEngine scenario

```javascript title={"voxeengine-grok-place-outbound-call.js"} maxLines={0}
require(Modules.Grok);
const SYSTEM_PROMPT = `
  Your name is Voxi. You are a helpful voice assistant for phone callers representing the company Voximplant (pronounced VOX-im-plant).
  Keep responses short and telephony-friendly (usually 1-2 sentences).
  If the user asks for a live agent or an operator, call the "forward_to_agent" function.
  If the user says goodbye, call the "hangup_call" function.
`;

// -------------------- Grok Voice Agent settings --------------------
const SESSION_PARAMETERS = {
    session: {
        voice: "Ara",
        turn_detection: {type: "server_vad"},
        instructions: SYSTEM_PROMPT,
        tools: [
            {type: "web_search"},
            {
                type: "file_search",
                vector_store_ids: ["collection_4c5a63ab-f739-4c13-93d2-05b74095c34a"], // optional RAG
                max_num_results: 5,
            },
            {
                type: "x_search",
                allowed_x_handles: ["voximplant", "aylarov"],
            },
            {
                type: "function",
                name: "forward_to_agent",
                description: "Forward the user to a live agent",
                parameters: {
                    type: "object",
                    properties: {},
                    required: [],
                },
            },
            {
                type: "function",
                name: "hangup_call",
                description: "Hangup the call",
                parameters: {
                    type: "object",
                    properties: {},
                    required: [],
                },
            },
        ],
    },
};

// Helper to parse custom data JSON from call setup
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, async () => {
    let voiceAIClient;
    let call;
    let hangupCall = false;
    let forwardToLiveAgent = false;

    const {destination, callerId} = parseCustomData();

    if (!destination || !callerId) {
        Logger.write(
            "Missing destination or callerId. Provide custom data: {\"destination\":\"+15551234567\",\"callerId\":\"+13322776633\"}",
        );
        VoxEngine.terminate();
        return;
    }

    call = VoxEngine.callPSTN(destination, callerId);
    // Alternative outbound paths (uncomment to use):
    // call = VoxEngine.callUser({username: destination, callerid: callerId});
    // call = VoxEngine.callSIP(`sip:${destination}@your-sip-domain`, callerId);
    // call = VoxEngine.callWhatsappUser({number: destination, callerid: callerId});

    // Termination functions - add cleanup and logging as needed
    call.addEventListener(CallEvents.Disconnected, ()=>VoxEngine.terminate());
    call.addEventListener(CallEvents.Failed, ()=>VoxEngine.terminate());

    try {
        voiceAIClient = await Grok.createVoiceAgentAPIClient({
            xAIApiKey: VoxEngine.getSecretValue('GROK_API_KEY'),
            onWebSocketClose: (event) => {
                Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
                VoxEngine.terminate();
            },
        });

        voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.ConversationCreated, (event) => {
            Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
            voiceAIClient.sessionUpdate(SESSION_PARAMETERS);
        });

        voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.SessionUpdated, (event) => {
            Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
            voiceAIClient.responseCreate({instructions: "Hello."});
        });

        // Keep it interruption-friendly (barge-in).
        voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.InputAudioBufferSpeechStarted, (event) => {
            Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
            voiceAIClient.clearMediaBuffer();
        });

        // Function calling.
        voiceAIClient.addEventListener(Grok.VoiceAgentAPIEvents.ResponseFunctionCallArgumentsDone, (event) => {
            Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);

            const {name, call_id} = event?.data?.payload || {};
            let output;

            if (name !== "forward_to_agent" && name !== "hangup_call") {
                Logger.write(`===Ignoring unhandled function call: ${name}===`);
                return;
            }

            if (name === "forward_to_agent") {
                forwardToLiveAgent = true;
                output = {result: "Forwarding your call to a live agent. Please hold on."};
            } else if (name === "hangup_call") {
                hangupCall = true;
                output = {result: "Have a great day, goodbye!"};
            }

            voiceAIClient.conversationItemCreate({
                item: {
                    type: "function_call_output",
                    call_id,
                    output: JSON.stringify(output),
                },
            });
            voiceAIClient.responseCreate({});
        });

        // -------------------- Log Other Events --------------------
        [
            CallEvents.FirstAudioPacketReceived,
            Grok.Events.WebSocketMediaStarted,
            Grok.VoiceAgentAPIEvents.InputAudioBufferSpeechStopped,
            Grok.VoiceAgentAPIEvents.ConversationItemInputAudioTranscriptionCompleted,
            Grok.VoiceAgentAPIEvents.ConversationItemAdded,
            Grok.VoiceAgentAPIEvents.ResponseCreated,
            Grok.VoiceAgentAPIEvents.ResponseOutputItemAdded,
            Grok.VoiceAgentAPIEvents.ResponseDone,
            Grok.VoiceAgentAPIEvents.ResponseOutputAudioTranscriptDelta,
            Grok.VoiceAgentAPIEvents.ResponseOutputAudioTranscriptDone,
            Grok.VoiceAgentAPIEvents.ResponseOutputAudioDelta,
            Grok.VoiceAgentAPIEvents.ResponseOutputAudioDone,
            Grok.VoiceAgentAPIEvents.ResponseOutputItemDone,
            Grok.VoiceAgentAPIEvents.ConnectorInformation,
            Grok.VoiceAgentAPIEvents.InputAudioBufferCommitted,
            Grok.VoiceAgentAPIEvents.WebSocketError,
            Grok.VoiceAgentAPIEvents.Unknown,
        ].forEach((evtName) => {
            voiceAIClient.addEventListener(evtName, (e) => {
                Logger.write(`===${e.name}===>${JSON.stringify(e)}`);
            });
        });

        voiceAIClient.addEventListener(Grok.Events.WebSocketMediaStarted, () => {
            VoxEngine.sendMediaBetween(call, voiceAIClient);
        });

        voiceAIClient.addEventListener(Grok.Events.WebSocketMediaEnded, (event) => {
            Logger.write(`===${event.name}===>${JSON.stringify(event.data)}`);
            if (hangupCall) callCloseHandler();
            else if (forwardToLiveAgent) {
                call.say("Here is where I would forward the call via the phone network or SIP.");
                call.addEventListener(CallEvents.PlaybackFinished, callCloseHandler);
            } else return;
        });
    } catch (error) {
        Logger.write("===SOMETHING_WENT_WRONG===");
        Logger.write(error);
        VoxEngine.terminate();
    }
});

```