From fe0f001eb4b6cb0668cb3c4a85794acf99ab49d9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 22 Oct 2025 15:12:07 +0000 Subject: [PATCH] Refactor dynamic call transfers to support two patterns Co-authored-by: sahil --- fern/call-forwarding.mdx | 7 ++ fern/calls/call-dynamic-transfers.mdx | 149 +++++++++++++++++++------- fern/server-url/events.mdx | 4 +- 3 files changed, 121 insertions(+), 39 deletions(-) diff --git a/fern/call-forwarding.mdx b/fern/call-forwarding.mdx index 58e1c75d8..e607e841e 100644 --- a/fern/call-forwarding.mdx +++ b/fern/call-forwarding.mdx @@ -11,6 +11,13 @@ Vapi's call forwarding functionality allows you to redirect calls to different p - **`transferCall` Tool**: This tool enables call forwarding to predefined phone numbers with specific messages based on the destination. + +Looking for dynamic routing decided at runtime? Use a `transferCall` tool with an empty `destinations` array and either: +- Have the assistant supply a destination parameter (e.g., `phoneNumber`) directly; no webhook is sent. +- Or respond from your server to the `transfer-destination-request` webhook with a destination. +See: Dynamic call transfers. + + ### Parameters and Messages - **Destinations**: A list of phone numbers where the call can be forwarded. diff --git a/fern/calls/call-dynamic-transfers.mdx b/fern/calls/call-dynamic-transfers.mdx index f3bcb323e..abced1498 100644 --- a/fern/calls/call-dynamic-transfers.mdx +++ b/fern/calls/call-dynamic-transfers.mdx @@ -24,16 +24,23 @@ Dynamic call transfers enable intelligent routing by determining transfer destin ## How It Works -Dynamic transfers operate by leaving the destination unspecified initially, then using webhooks to determine the appropriate destination when needed. +Dynamic transfers support two patterns. Choose one per your architecture: -**Transfer flow:** -1. **Trigger** - Voice agent determines a transfer is needed based on conversation -2. **Webhook** - Vapi sends `transfer-destination-request` to your server with call context -3. **Decision** - Your server analyzes context and external data to determine routing -4. **Response** - Server returns destination details and transfer configuration -5. **Transfer** - Vapi executes the transfer to the determined destination +1) **Assistant-supplied destination (no webhook)** + - The transfer tool includes custom parameters (e.g., `phoneNumber`). + - The assistant determines the destination (via reasoning or tools) and calls the transfer tool with that parameter. + - Vapi executes the transfer directly. The `transfer-destination-request` webhook is not sent. -**Available context:** Your webhook receives conversation transcript, extracted variables, customer information, function parameters, and call metadata. +2) **Server-supplied destination (webhook)** + - The transfer tool has an empty `destinations` array and no destination parameter is provided by the assistant. + - Vapi sends a `transfer-destination-request` to your server. + - Your server decides the destination and responds with it. + +**Available context to servers (webhook pattern):** Your webhook receives conversation transcript, extracted variables, function parameters (if any), and call metadata. + + +Parameters for transfer tools are fully customizable. You can name and structure them however you like to guide routing (for example `phoneNumber`, `department`, `reason`, `urgency`, etc.). + --- @@ -46,9 +53,12 @@ Dynamic transfers operate by leaving the destination unspecified initially, then - Navigate to **Tools** in your dashboard - Click **Create Tool** - Select **Transfer Call** as the tool type - - **Important**: Leave the destinations array empty - this creates a dynamic transfer tool + - **Important**: Leave the destinations array empty — this enables dynamic routing - Set function name: `dynamicTransfer` - - Add description explaining when this tool should be used + - Add a description describing when this tool should be used + - Decide your pattern: + - If the assistant will provide the destination: add a custom parameter like `phoneNumber` + - If your server will provide the destination: omit destination params and add any context params you want (e.g., `reason`, `urgency`) ```typescript @@ -56,30 +66,46 @@ Dynamic transfers operate by leaving the destination unspecified initially, then const vapi = new VapiClient({ token: process.env.VAPI_API_KEY }); + // Variant A: Assistant-supplied destination (no webhook) const dynamicTool = await vapi.tools.create({ type: "transferCall", // Empty destinations array makes this dynamic destinations: [], function: { name: "dynamicTransfer", - description: "Transfer call to appropriate destination based on customer needs", + description: "Transfer the call to a specific phone number (assistant provides it)", parameters: { type: "object", properties: { - reason: { - type: "string", - description: "Reason for transfer" - }, - urgency: { + phoneNumber: { type: "string", - enum: ["low", "medium", "high", "critical"] + description: "Destination in E.164 format (e.g., +19087528187)" } - } + }, + required: ["phoneNumber"] } } }); console.log(`Dynamic transfer tool created: ${dynamicTool.id}`); + + // Variant B: Server-supplied destination (webhook) + const webhookDrivenTool = await vapi.tools.create({ + type: "transferCall", + destinations: [], + function: { + name: "dynamicTransfer", + description: "Initiate a transfer and let the server choose destination", + parameters: { + type: "object", + properties: { + reason: { type: "string", description: "Reason for transfer" }, + urgency: { type: "string", enum: ["low", "medium", "high", "critical"] } + } + } + } + }); + console.log(`Webhook-driven transfer tool created: ${webhookDrivenTool.id}`); ``` @@ -87,44 +113,82 @@ Dynamic transfers operate by leaving the destination unspecified initially, then import requests import os - def create_dynamic_transfer_tool(): + def create_dynamic_transfer_tools(): url = "https://api.vapi.ai/tool" headers = { "Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}", "Content-Type": "application/json" } - - data = { + + # Variant A: Assistant-supplied destination (no webhook) + assistant_supplied = { "type": "transferCall", - "destinations": [], # Empty for dynamic routing + "destinations": [], "function": { "name": "dynamicTransfer", - "description": "Transfer call to appropriate destination based on customer needs", + "description": "Transfer the call to a specific phone number (assistant provides it)", "parameters": { "type": "object", "properties": { - "reason": { + "phoneNumber": { "type": "string", - "description": "Reason for transfer" - }, - "urgency": { - "type": "string", - "enum": ["low", "medium", "high", "critical"] + "description": "Destination in E.164 format (e.g., +19087528187)" } + }, + "required": ["phoneNumber"] + } + } + } + + # Variant B: Server-supplied destination (webhook) + webhook_driven = { + "type": "transferCall", + "destinations": [], + "function": { + "name": "dynamicTransfer", + "description": "Initiate a transfer and let the server choose destination", + "parameters": { + "type": "object", + "properties": { + "reason": {"type": "string", "description": "Reason for transfer"}, + "urgency": {"type": "string", "enum": ["low", "medium", "high", "critical"]} } } } } - - response = requests.post(url, headers=headers, json=data) - return response.json() - tool = create_dynamic_transfer_tool() - print(f"Dynamic transfer tool created: {tool['id']}") + a = requests.post(url, headers=headers, json=assistant_supplied).json() + b = requests.post(url, headers=headers, json=webhook_driven).json() + return a, b + + assistant_tool, server_tool = create_dynamic_transfer_tools() + print(f"Assistant-supplied tool: {assistant_tool['id']}") + print(f"Webhook-driven tool: {server_tool['id']}") ``` ```bash + # Variant A: Assistant-supplied destination (no webhook) + curl -X POST https://api.vapi.ai/tool \ + -H "Authorization: Bearer $VAPI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "transferCall", + "destinations": [], + "function": { + "name": "dynamicTransfer", + "description": "Transfer the call to a specific phone number (assistant provides it)", + "parameters": { + "type": "object", + "properties": { + "phoneNumber": {"type": "string", "description": "Destination in E.164 format (e.g., +19087528187)"} + }, + "required": ["phoneNumber"] + } + } + }' + + # Variant B: Server-supplied destination (webhook) curl -X POST https://api.vapi.ai/tool \ -H "Authorization: Bearer $VAPI_API_KEY" \ -H "Content-Type: application/json" \ @@ -133,11 +197,11 @@ Dynamic transfers operate by leaving the destination unspecified initially, then "destinations": [], "function": { "name": "dynamicTransfer", - "description": "Transfer call to appropriate destination based on customer needs", + "description": "Initiate a transfer and let the server choose destination", "parameters": { "type": "object", "properties": { - "reason": {"type": "string", "description": "Reason for transfer"}, + "reason": {"type": "string"}, "urgency": {"type": "string", "enum": ["low", "medium", "high", "critical"]} } } @@ -154,8 +218,7 @@ Dynamic transfers operate by leaving the destination unspecified initially, then - Navigate to **Assistants** - Create a new assistant or edit an existing one - Add your dynamic transfer tool to the assistant - - Enable the **transfer-destination-request** server event - - Set your server URL to handle the webhook + - Optional: Enable the **transfer-destination-request** server event and set your server URL if using the server-supplied pattern. This is not required when the assistant provides `phoneNumber` directly. ```typescript @@ -237,6 +300,10 @@ Dynamic transfers operate by leaving the destination unspecified initially, then + + This step is only required for the server-supplied destination pattern. + If your assistant provides a `phoneNumber` directly when calling the transfer tool, the `transfer-destination-request` webhook will not be sent. + ```typescript @@ -465,6 +532,12 @@ Dynamic transfers operate by leaving the destination unspecified initially, then **Security considerations:** Always verify webhook signatures to ensure requests come from Vapi. Never log sensitive customer data, implement proper access controls, and follow privacy regulations like GDPR and CCPA when handling customer information in routing decisions. +## Troubleshooting + +- If transfers work but you never see `transfer-destination-request` on your webhook, your assistant likely provided the destination (e.g., `phoneNumber`) directly in the tool call. This is expected and no webhook will be sent in that case. +- If you expect a webhook but it's not firing, ensure your transfer tool has an empty `destinations` array and the assistant is not supplying a destination parameter. +- If the assistant transfers to an unexpected number, audit your prompts, tools that return numbers, and any variables the assistant can access. + ## Related Documentation * **[Call Forwarding](/call-forwarding)** - Static transfer options and transfer plans diff --git a/fern/server-url/events.mdx b/fern/server-url/events.mdx index 59cdcd87e..0a33f737b 100644 --- a/fern/server-url/events.mdx +++ b/fern/server-url/events.mdx @@ -302,7 +302,7 @@ Tokens or tool-call outputs as the model generates. ### Transfer Destination Request -Requested when the model wants to transfer but the destination is not yet known. +Requested when the model wants to transfer but the destination is not yet known and must be provided by your server. ```json { @@ -313,6 +313,8 @@ Requested when the model wants to transfer but the destination is not yet known. } ``` +This event is emitted only if the assistant did not supply a destination when calling a `transferCall` tool (for example, it did not include a custom parameter like `phoneNumber`). If the assistant includes the destination directly, Vapi will transfer immediately and will not send this webhook. + Respond with a destination and optionally a message: ```json