> 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 bridges audio to Ultravox after the callee answers.

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

This example starts a VoxEngine session, places an outbound PSTN call, and bridges audio to Ultravox after the callee answers.

**⬇️ Jump to the [Full VoxEngine scenario](#full-voxengine-scenario).**

## Prerequisites

* Create a routing rule with this scenario attached (outgoing calls don’t use patterns) so you can run it from the Control Panel or trigger it via API: [https://voximplant.com/docs/getting-started/basic-concepts/routing-rules](https://voximplant.com/docs/getting-started/basic-concepts/routing-rules)
* Have a PSTN `destination` number you can dial (E.164 format), for example your mobile number.
* Configure a valid outbound `callerId` (for example, a rented Voximplant number or a verified caller ID): [https://voximplant.com/docs/getting-started/basic-concepts/phone-numbers](https://voximplant.com/docs/getting-started/basic-concepts/phone-numbers)
* Pass `destination` and `callerId` as routing rule custom data (this example reads them from `VoxEngine.customData()`): `{"destination":"+15551234567","callerId":"+15557654321"}`.
* Store your Ultravox API key in Voximplant [Secrets](/platform/voxengine/secrets) under `ULTRAVOX_API_KEY`.
* Configure tools (function calling): [https://docs.ultravox.ai/tools/overview](https://docs.ultravox.ai/tools/overview)

<Info title="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](https://voximplant.com/docs/getting-started/basic-concepts/phone-numbers).
</Info>

## 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`:

```json title="Custom data example"
{"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](https://voximplant.com/docs/references/httpapi/scenarios#startscenarios)

## Place the outbound call

Outbound calls are placed with `VoxEngine.callPSTN(number, callerid, parameters?)`.

In the full example, see `VoxEngine.customData()`, the `destination` / `callerId` parse, and the `AppEvents.Started` handler:

```js title="Place the call"
call = VoxEngine.callPSTN(destination, callerId);
call.addEventListener(CallEvents.Connected, async () => {
  // ...
});
```

## Alternate outbound destinations

The example includes **commented‑out** alternatives to place calls to other endpoints. Use these when you want to avoid PSTN or route internally:

* **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)

## Session setup

Ultravox uses a WebSocket API client created via `Ultravox.createWebSocketAPIClient(...)`. The key inputs are:

* `endpoint`: use `Ultravox.HTTPEndpoint.CREATE_CALL` (or `CREATE_AGENT_CALL`).
* `authorizations`: include `X-API-Key` with your Ultravox API key.
* `body`: provide `systemPrompt`, `model`, and `voice`.

## Connect call audio

For outbound, create the Ultravox client and bridge audio **after the callee answers** (so the agent doesn’t speak into ringback):

```js title="Create the client and bridge audio"
voiceAIClient = await Ultravox.createWebSocketAPIClient({ ... });
VoxEngine.sendMediaBetween(call, voiceAIClient);
```

## Barge-in

Clear the output buffer when the caller starts talking to keep the conversation interruption‑friendly:

```js title="Barge-in"
voiceAIClient.addEventListener(Ultravox.WebSocketAPIEvents.Transcript, (event) => {
  const { role } = event?.data?.payload || {};
  if (role === "user") voiceAIClient.clearMediaBuffer();
});
```

## Full VoxEngine scenario

```javascript title={"voxeengine-ultravox-place-outbound-call.js"} maxLines={0}
/**
 * Voximplant + Ultravox WebSocket API connector demo
 * Scenario: place an outbound call and bridge it to Ultravox after answer.
 */

require(Modules.Ultravox);
const SYSTEM_PROMPT = `You are a helpful phone assistant for Voximplant callers.
Keep responses short and telephony-friendly (1–2 sentences).`;

// -------------------- Ultravox WebSocket API settings --------------------
const MODEL = "ultravox-v0.7";
const VOICE_NAME = "Mark";
const DEFAULT_CALL_DURATION_MS = 40000;

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;

    const {
        destination,
        callerId,
        callDurationMs = DEFAULT_CALL_DURATION_MS,
    } = parseCustomData();

    if (!destination || !callerId) {
        Logger.write("===MISSING_CUSTOM_DATA===");
        Logger.write(JSON.stringify({destination, callerId}));
        VoxEngine.terminate();
        return;
    }

    // Place the outbound call (primary path: PSTN)
    let call = VoxEngine.callPSTN(destination, callerId);

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

    let hangupTimer;

    // Termination functions - add cleanup and logging as needed
    const terminate = () => {
        if (hangupTimer) clearTimeout(hangupTimer);
        voiceAIClient?.close();
        VoxEngine.terminate();
    };

    call.addEventListener(CallEvents.Disconnected, terminate);
    call.addEventListener(CallEvents.Failed, terminate);

    call.addEventListener(CallEvents.Connected, async () => {
        hangupTimer = setTimeout(() => call.hangup(), Number(callDurationMs));

        const apiKey = VoxEngine.getSecretValue('ULTRAVOX_API_KEY');
        const authorizations = {
            "X-API-Key": apiKey,
        };

        // Create client and wire media after the callee answers
        const webSocketAPIClientParameters = {
            endpoint: Ultravox.HTTPEndpoint.CREATE_CALL,
            authorizations,
            body: {
                systemPrompt: SYSTEM_PROMPT,
                model: MODEL,
                voice: VOICE_NAME,
            },
            onWebSocketClose: (event) => {
                Logger.write("===ULTRAVOX_WEBSOCKET_CLOSED===");
                if (event) Logger.write(JSON.stringify(event));
                terminate();
            },
        };

        try {
            voiceAIClient = await Ultravox.createWebSocketAPIClient(
                webSocketAPIClientParameters,
            );

            VoxEngine.sendMediaBetween(call, voiceAIClient);

            // ---------------------- Event handlers -----------------------
            // Barge-in: keep conversation responsive and capture transcript
            voiceAIClient.addEventListener(
                Ultravox.WebSocketAPIEvents.Transcript,
                (event) => {
                    const payload = event?.data?.payload || event?.data || {};
                    const role = payload.role;
                    const text = payload.text || payload.delta;

                    if (role && text) Logger.write(`===TRANSCRIPT=== ${role}: ${text}`);
                    if (role === "user") voiceAIClient.clearMediaBuffer();
                },
            );

            voiceAIClient.addEventListener(
                Ultravox.WebSocketAPIEvents.PlaybackClearBuffer,
                () => voiceAIClient.clearMediaBuffer(),
            );

            // Consolidated "log-only" handlers - key Ultravox/VoxEngine debugging events
            [
                Ultravox.WebSocketAPIEvents.ConnectorInformation,
                Ultravox.WebSocketAPIEvents.HTTPResponse,
                Ultravox.WebSocketAPIEvents.State,
                Ultravox.WebSocketAPIEvents.Debug,
                Ultravox.WebSocketAPIEvents.WebSocketError,
                Ultravox.WebSocketAPIEvents.Unknown,
                Ultravox.Events.WebSocketMediaStarted,
                Ultravox.Events.WebSocketMediaEnded,
            ].forEach((eventName) => {
                voiceAIClient.addEventListener(eventName, (event) => {
                    Logger.write(`===${event.name}===`);
                    Logger.write(JSON.stringify(event));
                });
            });
        } catch (error) {
            Logger.write("===SOMETHING_WENT_WRONG===");
            Logger.write(error);
            terminate();
        }
    });
});

```