Skip to content

Commit 4ad6633

Browse files
Refactor dynamic call transfers to support two patterns (#754)
1 parent b8c5254 commit 4ad6633

File tree

3 files changed

+121
-39
lines changed

3 files changed

+121
-39
lines changed

fern/call-forwarding.mdx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ Vapi's call forwarding functionality allows you to redirect calls to different p
1111

1212
- **`transferCall` Tool**: This tool enables call forwarding to predefined phone numbers with specific messages based on the destination.
1313

14+
<Note>
15+
Looking for dynamic routing decided at runtime? Use a `transferCall` tool with an empty `destinations` array and either:
16+
- Have the assistant supply a destination parameter (e.g., `phoneNumber`) directly; no webhook is sent.
17+
- Or respond from your server to the `transfer-destination-request` webhook with a destination.
18+
See: <a href="/calls/call-dynamic-transfers">Dynamic call transfers</a>.
19+
</Note>
20+
1421
### Parameters and Messages
1522

1623
- **Destinations**: A list of phone numbers where the call can be forwarded.

fern/calls/call-dynamic-transfers.mdx

Lines changed: 111 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,23 @@ Dynamic call transfers enable intelligent routing by determining transfer destin
2424

2525
## How It Works
2626

27-
Dynamic transfers operate by leaving the destination unspecified initially, then using webhooks to determine the appropriate destination when needed.
27+
Dynamic transfers support two patterns. Choose one per your architecture:
2828

29-
**Transfer flow:**
30-
1. **Trigger** - Voice agent determines a transfer is needed based on conversation
31-
2. **Webhook** - Vapi sends `transfer-destination-request` to your server with call context
32-
3. **Decision** - Your server analyzes context and external data to determine routing
33-
4. **Response** - Server returns destination details and transfer configuration
34-
5. **Transfer** - Vapi executes the transfer to the determined destination
29+
1) **Assistant-supplied destination (no webhook)**
30+
- The transfer tool includes custom parameters (e.g., `phoneNumber`).
31+
- The assistant determines the destination (via reasoning or tools) and calls the transfer tool with that parameter.
32+
- Vapi executes the transfer directly. The `transfer-destination-request` webhook is not sent.
3533

36-
**Available context:** Your webhook receives conversation transcript, extracted variables, customer information, function parameters, and call metadata.
34+
2) **Server-supplied destination (webhook)**
35+
- The transfer tool has an empty `destinations` array and no destination parameter is provided by the assistant.
36+
- Vapi sends a `transfer-destination-request` to your server.
37+
- Your server decides the destination and responds with it.
38+
39+
**Available context to servers (webhook pattern):** Your webhook receives conversation transcript, extracted variables, function parameters (if any), and call metadata.
40+
41+
<Tip>
42+
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.).
43+
</Tip>
3744

3845
---
3946

@@ -46,85 +53,142 @@ Dynamic transfers operate by leaving the destination unspecified initially, then
4653
- Navigate to **Tools** in your dashboard
4754
- Click **Create Tool**
4855
- Select **Transfer Call** as the tool type
49-
- **Important**: Leave the destinations array empty - this creates a dynamic transfer tool
56+
- **Important**: Leave the destinations array empty this enables dynamic routing
5057
- Set function name: `dynamicTransfer`
51-
- Add description explaining when this tool should be used
58+
- Add a description describing when this tool should be used
59+
- Decide your pattern:
60+
- If the assistant will provide the destination: add a custom parameter like `phoneNumber`
61+
- If your server will provide the destination: omit destination params and add any context params you want (e.g., `reason`, `urgency`)
5262
</Tab>
5363
<Tab title="TypeScript (Server SDK)">
5464
```typescript
5565
import { VapiClient } from "@vapi-ai/server-sdk";
5666

5767
const vapi = new VapiClient({ token: process.env.VAPI_API_KEY });
5868

69+
// Variant A: Assistant-supplied destination (no webhook)
5970
const dynamicTool = await vapi.tools.create({
6071
type: "transferCall",
6172
// Empty destinations array makes this dynamic
6273
destinations: [],
6374
function: {
6475
name: "dynamicTransfer",
65-
description: "Transfer call to appropriate destination based on customer needs",
76+
description: "Transfer the call to a specific phone number (assistant provides it)",
6677
parameters: {
6778
type: "object",
6879
properties: {
69-
reason: {
70-
type: "string",
71-
description: "Reason for transfer"
72-
},
73-
urgency: {
80+
phoneNumber: {
7481
type: "string",
75-
enum: ["low", "medium", "high", "critical"]
82+
description: "Destination in E.164 format (e.g., +19087528187)"
7683
}
77-
}
84+
},
85+
required: ["phoneNumber"]
7886
}
7987
}
8088
});
8189

8290
console.log(`Dynamic transfer tool created: ${dynamicTool.id}`);
91+
92+
// Variant B: Server-supplied destination (webhook)
93+
const webhookDrivenTool = await vapi.tools.create({
94+
type: "transferCall",
95+
destinations: [],
96+
function: {
97+
name: "dynamicTransfer",
98+
description: "Initiate a transfer and let the server choose destination",
99+
parameters: {
100+
type: "object",
101+
properties: {
102+
reason: { type: "string", description: "Reason for transfer" },
103+
urgency: { type: "string", enum: ["low", "medium", "high", "critical"] }
104+
}
105+
}
106+
}
107+
});
108+
console.log(`Webhook-driven transfer tool created: ${webhookDrivenTool.id}`);
83109
```
84110
</Tab>
85111
<Tab title="Python (Server SDK)">
86112
```python
87113
import requests
88114
import os
89115

90-
def create_dynamic_transfer_tool():
116+
def create_dynamic_transfer_tools():
91117
url = "https://api.vapi.ai/tool"
92118
headers = {
93119
"Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}",
94120
"Content-Type": "application/json"
95121
}
96-
97-
data = {
122+
123+
# Variant A: Assistant-supplied destination (no webhook)
124+
assistant_supplied = {
98125
"type": "transferCall",
99-
"destinations": [], # Empty for dynamic routing
126+
"destinations": [],
100127
"function": {
101128
"name": "dynamicTransfer",
102-
"description": "Transfer call to appropriate destination based on customer needs",
129+
"description": "Transfer the call to a specific phone number (assistant provides it)",
103130
"parameters": {
104131
"type": "object",
105132
"properties": {
106-
"reason": {
133+
"phoneNumber": {
107134
"type": "string",
108-
"description": "Reason for transfer"
109-
},
110-
"urgency": {
111-
"type": "string",
112-
"enum": ["low", "medium", "high", "critical"]
135+
"description": "Destination in E.164 format (e.g., +19087528187)"
113136
}
137+
},
138+
"required": ["phoneNumber"]
139+
}
140+
}
141+
}
142+
143+
# Variant B: Server-supplied destination (webhook)
144+
webhook_driven = {
145+
"type": "transferCall",
146+
"destinations": [],
147+
"function": {
148+
"name": "dynamicTransfer",
149+
"description": "Initiate a transfer and let the server choose destination",
150+
"parameters": {
151+
"type": "object",
152+
"properties": {
153+
"reason": {"type": "string", "description": "Reason for transfer"},
154+
"urgency": {"type": "string", "enum": ["low", "medium", "high", "critical"]}
114155
}
115156
}
116157
}
117158
}
118-
119-
response = requests.post(url, headers=headers, json=data)
120-
return response.json()
121159

122-
tool = create_dynamic_transfer_tool()
123-
print(f"Dynamic transfer tool created: {tool['id']}")
160+
a = requests.post(url, headers=headers, json=assistant_supplied).json()
161+
b = requests.post(url, headers=headers, json=webhook_driven).json()
162+
return a, b
163+
164+
assistant_tool, server_tool = create_dynamic_transfer_tools()
165+
print(f"Assistant-supplied tool: {assistant_tool['id']}")
166+
print(f"Webhook-driven tool: {server_tool['id']}")
124167
```
125168
</Tab>
126169
<Tab title="cURL">
127170
```bash
171+
# Variant A: Assistant-supplied destination (no webhook)
172+
curl -X POST https://api.vapi.ai/tool \
173+
-H "Authorization: Bearer $VAPI_API_KEY" \
174+
-H "Content-Type: application/json" \
175+
-d '{
176+
"type": "transferCall",
177+
"destinations": [],
178+
"function": {
179+
"name": "dynamicTransfer",
180+
"description": "Transfer the call to a specific phone number (assistant provides it)",
181+
"parameters": {
182+
"type": "object",
183+
"properties": {
184+
"phoneNumber": {"type": "string", "description": "Destination in E.164 format (e.g., +19087528187)"}
185+
},
186+
"required": ["phoneNumber"]
187+
}
188+
}
189+
}'
190+
191+
# Variant B: Server-supplied destination (webhook)
128192
curl -X POST https://api.vapi.ai/tool \
129193
-H "Authorization: Bearer $VAPI_API_KEY" \
130194
-H "Content-Type: application/json" \
@@ -133,11 +197,11 @@ Dynamic transfers operate by leaving the destination unspecified initially, then
133197
"destinations": [],
134198
"function": {
135199
"name": "dynamicTransfer",
136-
"description": "Transfer call to appropriate destination based on customer needs",
200+
"description": "Initiate a transfer and let the server choose destination",
137201
"parameters": {
138202
"type": "object",
139203
"properties": {
140-
"reason": {"type": "string", "description": "Reason for transfer"},
204+
"reason": {"type": "string"},
141205
"urgency": {"type": "string", "enum": ["low", "medium", "high", "critical"]}
142206
}
143207
}
@@ -154,8 +218,7 @@ Dynamic transfers operate by leaving the destination unspecified initially, then
154218
- Navigate to **Assistants**
155219
- Create a new assistant or edit an existing one
156220
- Add your dynamic transfer tool to the assistant
157-
- Enable the **transfer-destination-request** server event
158-
- Set your server URL to handle the webhook
221+
- 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.
159222
</Tab>
160223
<Tab title="TypeScript (Server SDK)">
161224
```typescript
@@ -237,6 +300,10 @@ Dynamic transfers operate by leaving the destination unspecified initially, then
237300
</Step>
238301

239302
<Step title="Build your webhook server">
303+
<Info>
304+
This step is only required for the <strong>server-supplied destination</strong> pattern.
305+
If your assistant provides a `phoneNumber` directly when calling the transfer tool, the `transfer-destination-request` webhook will not be sent.
306+
</Info>
240307
<Tabs>
241308
<Tab title="Node.js (Express)">
242309
```typescript
@@ -465,6 +532,12 @@ Dynamic transfers operate by leaving the destination unspecified initially, then
465532
**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.
466533
</Warning>
467534

535+
## Troubleshooting
536+
537+
- 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.
538+
- 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.
539+
- If the assistant transfers to an unexpected number, audit your prompts, tools that return numbers, and any variables the assistant can access.
540+
468541
## Related Documentation
469542

470543
* **[Call Forwarding](/call-forwarding)** - Static transfer options and transfer plans

fern/server-url/events.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ Tokens or tool-call outputs as the model generates.
302302

303303
### Transfer Destination Request
304304

305-
Requested when the model wants to transfer but the destination is not yet known.
305+
Requested when the model wants to transfer but the destination is not yet known and must be provided by your server.
306306

307307
```json
308308
{
@@ -313,6 +313,8 @@ Requested when the model wants to transfer but the destination is not yet known.
313313
}
314314
```
315315

316+
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.
317+
316318
Respond with a destination and optionally a message:
317319

318320
```json

0 commit comments

Comments
 (0)