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

# Example: Function calling

> This example answers an inbound call, connects it to Ultravox, and handles **client tool invocations** via Ultravox data messages.

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

This example answers an inbound call, connects it to Ultravox, and handles **client tool invocations** via Ultravox data messages.

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

## Prerequisites

* Set up an inbound entrypoint for the caller:
  * Phone number: [https://voximplant.com/docs/getting-started/basic-concepts/phone-numbers](https://voximplant.com/docs/getting-started/basic-concepts/phone-numbers)
  * WhatsApp: [https://voximplant.com/docs/guides/integrations/whatsapp](https://voximplant.com/docs/guides/integrations/whatsapp)
  * SIP user / SIP registration: [https://voximplant.com/docs/guides/calls/sip](https://voximplant.com/docs/guides/calls/sip)
  * App user: [https://voximplant.com/docs/getting-started/basic-concepts/users](https://voximplant.com/docs/getting-started/basic-concepts/users) (see also [https://voximplant.com/docs/guides/calls/scenarios#how-to-call-a-voximplant-user](https://voximplant.com/docs/guides/calls/scenarios#how-to-call-a-voximplant-user))
* Create a routing rule that points the destination (phone number / WhatsApp / SIP username / app user alias) to this scenario: [https://voximplant.com/docs/getting-started/basic-concepts/routing-rules](https://voximplant.com/docs/getting-started/basic-concepts/routing-rules)
* Store your Ultravox API key in Voximplant [Secrets](/platform/voxengine/secrets) under `ULTRAVOX_API_KEY`.
* Configure tools in Ultravox (agent or call config) so tool invocations are emitted during the call.

## Tool invocation flow

Ultravox emits a `ClientToolInvocation` data message when the agent calls a client tool. The event payload includes:

* `toolName`
* `invocationId`
* `parameters` (tool input)

The example listens for these events and returns a `client_tool_result` response using `voiceAIClient.clientToolResult(...)`.

```js title="Tool response (from the example)"
voiceAIClient.clientToolResult({
  type: "client_tool_result",
  invocationId,
  responseType: "tool-response",
  agentReaction: "speaks",
  result: JSON.stringify({ location, temperature_f: 72, condition: "sunny" }),
});
```

## Full VoxEngine scenario

```javascript title={"voxeengine-ultravox-function-calling.js"} maxLines={0}
/**
 * Voximplant + Ultravox WebSocket API connector demo
 * Scenario: answer an incoming call and handle client tool invocations.
 */

require(Modules.Ultravox);
const SYSTEM_PROMPT = `You are a helpful phone assistant for Voximplant callers.
If you need external data, call the get_weather tool.
If the caller wants to end the call, call the hangup_call tool.`;

// -------------------- Ultravox WebSocket API settings --------------------
const AGENT_CONFIG = {
    systemPrompt: SYSTEM_PROMPT,
    model: "ultravox-v0.7",
    voice: "Mark",
};
const WEATHER_TOOL_NAME = "get_weather";    // Needs to be defined in the Ultravox portal
const HANGUP_TOOL_NAME = "hangUp";          // Built-in Ultravox tool

VoxEngine.addEventListener(AppEvents.CallAlerting, async ({call}) => {
    let voiceAIClient;
    let hangupAfterResponse = false;
    let hangupTimer;

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

    try {
        call.answer();
        // call.record({ hd_audio: true, stereo: true }); // Optional: record the call

        // Create client and wire media
        voiceAIClient = await Ultravox.createWebSocketAPIClient(
            {
                endpoint: Ultravox.HTTPEndpoint.CREATE_CALL,
                authorizations: {"X-API-Key": VoxEngine.getSecretValue('ULTRAVOX_API_KEY')},
                body: AGENT_CONFIG,
                onWebSocketClose: (event) => {
                    Logger.write("===ULTRAVOX_WEBSOCKET_CLOSED===");
                    if (event) Logger.write(JSON.stringify(event));
                    VoxEngine.terminate();
                },
            },
        );

        VoxEngine.sendMediaBetween(call, voiceAIClient);

        // ---------------------- Event handlers -----------------------
        // Function calling: handle tool requests and send back responses
        voiceAIClient.addEventListener(
            Ultravox.WebSocketAPIEvents.ClientToolInvocation,
            (event) => {
                const payload = event?.data?.payload || event?.data || {};
                const {toolName, invocationId, parameters} = payload;

                if (!toolName || !invocationId) return;

                if (toolName !== WEATHER_TOOL_NAME && toolName !== HANGUP_TOOL_NAME) {
                    voiceAIClient.clientToolResult({
                        type: "client_tool_result",
                        invocationId,
                        errorType: "tool_not_found",
                        errorMessage: `Unhandled tool: ${toolName}`,
                    });
                    return;
                }

                if (toolName === WEATHER_TOOL_NAME) {
                    const location = parameters?.location || "Unknown";
                    const result = JSON.stringify({
                        location,
                        temperature_f: 72,
                        condition: "sunny",
                    });

                    voiceAIClient.clientToolResult({
                        type: "client_tool_result",
                        invocationId,
                        responseType: "tool-response",
                        agentReaction: "speaks",
                        result,
                    });
                    return;
                }

                if (toolName === HANGUP_TOOL_NAME) {
                    hangupAfterResponse = true;
                    voiceAIClient.clientToolResult({
                        type: "client_tool_result",
                        invocationId,
                        responseType: "tool-response",
                        agentReaction: "speaks",
                        result: JSON.stringify({ result: "Hanging up now. Goodbye!" }),
                    });
                }
            },
        );

        // 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();
                if (hangupAfterResponse && role === "assistant" && payload.text) {
                    if (hangupTimer) clearTimeout(hangupTimer);
                    hangupTimer = setTimeout(() => call.hangup(), 1200);
                }
            },
        );

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

        // If the media stream ends after a goodbye response, hang up the call.
        voiceAIClient.addEventListener(Ultravox.Events.WebSocketMediaEnded, () => {
            if (hangupAfterResponse) call.hangup();
        });

        // 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);
        VoxEngine.terminate();
    }
});

```