Skip to content

Commit 62f880f

Browse files
committed
docs: update dynamic call transfer examples
1 parent 2c480b3 commit 62f880f

File tree

4 files changed

+732
-417
lines changed

4 files changed

+732
-417
lines changed

fern/calls/call-dynamic-transfers.mdx

Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Dynamic call transfers enable intelligent routing by determining transfer destin
1616
* Context-aware transfers with conversation summaries
1717
* Fallback handling for unavailable destinations
1818

19+
## Prerequisites
20+
21+
* A [Vapi account](https://dashboard.vapi.ai/)
22+
* A server or cloud function that can receive webhooks from Vapi
23+
* (Optional) CRM system or customer database for enhanced routing logic
24+
1925
## How It Works
2026

2127
Dynamic transfers operate by leaving the destination unspecified initially, then using webhooks to determine the appropriate destination when needed.
@@ -29,6 +35,385 @@ Dynamic transfers operate by leaving the destination unspecified initially, then
2935

3036
**Available context:** Your webhook receives conversation transcript, extracted variables, customer information, function parameters, and call metadata.
3137

38+
---
39+
40+
## Quick Implementation Guide
41+
42+
<Steps>
43+
<Step title="Create a dynamic transfer tool">
44+
<Tabs>
45+
<Tab title="Dashboard">
46+
- Navigate to **Tools** in your dashboard
47+
- Click **Create Tool**
48+
- Select **Transfer Call** as the tool type
49+
- **Important**: Leave the destinations array empty - this creates a dynamic transfer tool
50+
- Set function name: `dynamicTransfer`
51+
- Add description explaining when this tool should be used
52+
</Tab>
53+
<Tab title="TypeScript (Server SDK)">
54+
```typescript
55+
import { VapiClient } from "@vapi-ai/server-sdk";
56+
57+
const vapi = new VapiClient({ token: process.env.VAPI_API_KEY });
58+
59+
const dynamicTool = await vapi.tools.create({
60+
type: "transferCall",
61+
// Empty destinations array makes this dynamic
62+
destinations: [],
63+
function: {
64+
name: "dynamicTransfer",
65+
description: "Transfer call to appropriate destination based on customer needs",
66+
parameters: {
67+
type: "object",
68+
properties: {
69+
reason: {
70+
type: "string",
71+
description: "Reason for transfer"
72+
},
73+
urgency: {
74+
type: "string",
75+
enum: ["low", "medium", "high", "critical"]
76+
}
77+
}
78+
}
79+
}
80+
});
81+
82+
console.log(`Dynamic transfer tool created: ${dynamicTool.id}`);
83+
```
84+
</Tab>
85+
<Tab title="Python (Server SDK)">
86+
```python
87+
import requests
88+
import os
89+
90+
def create_dynamic_transfer_tool():
91+
url = "https://api.vapi.ai/tool"
92+
headers = {
93+
"Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}",
94+
"Content-Type": "application/json"
95+
}
96+
97+
data = {
98+
"type": "transferCall",
99+
"destinations": [], # Empty for dynamic routing
100+
"function": {
101+
"name": "dynamicTransfer",
102+
"description": "Transfer call to appropriate destination based on customer needs",
103+
"parameters": {
104+
"type": "object",
105+
"properties": {
106+
"reason": {
107+
"type": "string",
108+
"description": "Reason for transfer"
109+
},
110+
"urgency": {
111+
"type": "string",
112+
"enum": ["low", "medium", "high", "critical"]
113+
}
114+
}
115+
}
116+
}
117+
}
118+
119+
response = requests.post(url, headers=headers, json=data)
120+
return response.json()
121+
122+
tool = create_dynamic_transfer_tool()
123+
print(f"Dynamic transfer tool created: {tool['id']}")
124+
```
125+
</Tab>
126+
<Tab title="cURL">
127+
```bash
128+
curl -X POST https://api.vapi.ai/tool \
129+
-H "Authorization: Bearer $VAPI_API_KEY" \
130+
-H "Content-Type: application/json" \
131+
-d '{
132+
"type": "transferCall",
133+
"destinations": [],
134+
"function": {
135+
"name": "dynamicTransfer",
136+
"description": "Transfer call to appropriate destination based on customer needs",
137+
"parameters": {
138+
"type": "object",
139+
"properties": {
140+
"reason": {"type": "string", "description": "Reason for transfer"},
141+
"urgency": {"type": "string", "enum": ["low", "medium", "high", "critical"]}
142+
}
143+
}
144+
}
145+
}'
146+
```
147+
</Tab>
148+
</Tabs>
149+
</Step>
150+
151+
<Step title="Create an assistant with the transfer tool">
152+
<Tabs>
153+
<Tab title="Dashboard">
154+
- Navigate to **Assistants**
155+
- Create a new assistant or edit an existing one
156+
- 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
159+
</Tab>
160+
<Tab title="TypeScript (Server SDK)">
161+
```typescript
162+
const assistant = await vapi.assistants.create({
163+
name: "Dynamic Transfer Assistant",
164+
firstMessage: "Hello! How can I help you today?",
165+
model: {
166+
provider: "openai",
167+
model: "gpt-4o",
168+
messages: [
169+
{
170+
role: "system",
171+
content: "You help customers and transfer them when needed using the dynamicTransfer tool. Assess the customer's needs and transfer to the appropriate department."
172+
}
173+
],
174+
toolIds: ["YOUR_DYNAMIC_TOOL_ID"]
175+
},
176+
voice: {
177+
provider: "11labs",
178+
voiceId: "burt"
179+
},
180+
serverUrl: "https://your-server.com/webhook",
181+
serverUrlSecret: process.env.WEBHOOK_SECRET
182+
});
183+
```
184+
</Tab>
185+
<Tab title="Python (Server SDK)">
186+
```python
187+
def create_assistant_with_dynamic_transfer(tool_id):
188+
url = "https://api.vapi.ai/assistant"
189+
headers = {
190+
"Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}",
191+
"Content-Type": "application/json"
192+
}
193+
194+
data = {
195+
"name": "Dynamic Transfer Assistant",
196+
"firstMessage": "Hello! How can I help you today?",
197+
"model": {
198+
"provider": "openai",
199+
"model": "gpt-4o",
200+
"messages": [{
201+
"role": "system",
202+
"content": "You help customers and transfer them when needed using the dynamicTransfer tool. Assess the customer's needs and transfer to the appropriate department."
203+
}],
204+
"toolIds": [tool_id]
205+
},
206+
"voice": {"provider": "11labs", "voiceId": "burt"},
207+
"serverUrl": "https://your-server.com/webhook",
208+
"serverUrlSecret": os.getenv("WEBHOOK_SECRET")
209+
}
210+
211+
response = requests.post(url, headers=headers, json=data)
212+
return response.json()
213+
```
214+
</Tab>
215+
<Tab title="cURL">
216+
```bash
217+
curl -X POST https://api.vapi.ai/assistant \
218+
-H "Authorization: Bearer $VAPI_API_KEY" \
219+
-H "Content-Type: application/json" \
220+
-d '{
221+
"name": "Dynamic Transfer Assistant",
222+
"firstMessage": "Hello! How can I help you today?",
223+
"model": {
224+
"provider": "openai",
225+
"model": "gpt-4o",
226+
"messages": [{
227+
"role": "system",
228+
"content": "You help customers and transfer them when needed using the dynamicTransfer tool."
229+
}],
230+
"toolIds": ["YOUR_DYNAMIC_TOOL_ID"]
231+
},
232+
"serverUrl": "https://your-server.com/webhook"
233+
}'
234+
```
235+
</Tab>
236+
</Tabs>
237+
</Step>
238+
239+
<Step title="Build your webhook server">
240+
<Tabs>
241+
<Tab title="Node.js (Express)">
242+
```typescript
243+
import express from 'express';
244+
import crypto from 'crypto';
245+
246+
const app = express();
247+
app.use(express.json());
248+
249+
function verifyWebhookSignature(payload: string, signature: string) {
250+
const expectedSignature = crypto
251+
.createHmac('sha256', process.env.WEBHOOK_SECRET!)
252+
.update(payload)
253+
.digest('hex');
254+
return crypto.timingSafeEqual(
255+
Buffer.from(signature),
256+
Buffer.from(expectedSignature)
257+
);
258+
}
259+
260+
app.post('/webhook', (req, res) => {
261+
try {
262+
const signature = req.headers['x-vapi-signature'] as string;
263+
const payload = JSON.stringify(req.body);
264+
265+
if (!verifyWebhookSignature(payload, signature)) {
266+
return res.status(401).json({ error: 'Invalid signature' });
267+
}
268+
269+
const request = req.body;
270+
271+
if (request.type !== 'transfer-destination-request') {
272+
return res.status(200).json({ received: true });
273+
}
274+
275+
// Simple routing logic - customize for your needs
276+
const { functionCall, customer } = request;
277+
const urgency = functionCall.parameters?.urgency || 'medium';
278+
279+
let destination;
280+
if (urgency === 'critical') {
281+
destination = {
282+
type: "number",
283+
number: "+1-555-EMERGENCY",
284+
message: "Connecting you to our emergency team."
285+
};
286+
} else {
287+
destination = {
288+
type: "number",
289+
number: "+1-555-SUPPORT",
290+
message: "Transferring you to our support team."
291+
};
292+
}
293+
294+
res.json({ destination });
295+
} catch (error) {
296+
console.error('Webhook error:', error);
297+
res.status(500).json({
298+
error: 'Transfer routing failed. Please try again.'
299+
});
300+
}
301+
});
302+
303+
app.listen(3000, () => {
304+
console.log('Webhook server running on port 3000');
305+
});
306+
```
307+
</Tab>
308+
<Tab title="Python (FastAPI)">
309+
```python
310+
import os
311+
import hmac
312+
import hashlib
313+
from fastapi import FastAPI, HTTPException, Request
314+
315+
app = FastAPI()
316+
317+
def verify_webhook_signature(payload: bytes, signature: str) -> bool:
318+
webhook_secret = os.getenv('WEBHOOK_SECRET', '').encode()
319+
expected_signature = hmac.new(
320+
webhook_secret, payload, hashlib.sha256
321+
).hexdigest()
322+
return hmac.compare_digest(signature, expected_signature)
323+
324+
@app.post("/webhook")
325+
async def handle_webhook(request: Request):
326+
try:
327+
body = await request.body()
328+
signature = request.headers.get('x-vapi-signature', '')
329+
330+
if not verify_webhook_signature(body, signature):
331+
raise HTTPException(status_code=401, detail="Invalid signature")
332+
333+
request_data = await request.json()
334+
335+
if request_data.get('type') != 'transfer-destination-request':
336+
return {"received": True}
337+
338+
# Simple routing logic - customize for your needs
339+
function_call = request_data.get('functionCall', {})
340+
urgency = function_call.get('parameters', {}).get('urgency', 'medium')
341+
342+
if urgency == 'critical':
343+
destination = {
344+
"type": "number",
345+
"number": "+1-555-EMERGENCY",
346+
"message": "Connecting you to our emergency team."
347+
}
348+
else:
349+
destination = {
350+
"type": "number",
351+
"number": "+1-555-SUPPORT",
352+
"message": "Transferring you to our support team."
353+
}
354+
355+
return {"destination": destination}
356+
357+
except Exception as error:
358+
print(f"Webhook error: {error}")
359+
raise HTTPException(
360+
status_code=500,
361+
detail="Transfer routing failed. Please try again."
362+
)
363+
```
364+
</Tab>
365+
</Tabs>
366+
</Step>
367+
368+
<Step title="Test your dynamic transfer system">
369+
<Tabs>
370+
<Tab title="Dashboard">
371+
- Create a phone number and assign your assistant
372+
- Call the number and test different transfer scenarios
373+
- Monitor your webhook server logs to see the routing decisions
374+
- Verify transfers are working to the correct destinations
375+
</Tab>
376+
<Tab title="TypeScript (Testing)">
377+
```typescript
378+
// Test with an outbound call
379+
const testCall = await vapi.calls.create({
380+
assistantId: "YOUR_ASSISTANT_ID",
381+
customer: {
382+
number: "+1234567890" // Your test number
383+
}
384+
});
385+
386+
console.log(`Test call created: ${testCall.id}`);
387+
388+
// Monitor webhook server logs to see transfer requests
389+
```
390+
</Tab>
391+
<Tab title="Python (Testing)">
392+
```python
393+
def test_dynamic_transfers(assistant_id):
394+
url = "https://api.vapi.ai/call"
395+
headers = {
396+
"Authorization": f"Bearer {os.getenv('VAPI_API_KEY')}",
397+
"Content-Type": "application/json"
398+
}
399+
400+
data = {
401+
"assistantId": assistant_id,
402+
"customer": {"number": "+1234567890"}
403+
}
404+
405+
response = requests.post(url, headers=headers, json=data)
406+
call = response.json()
407+
print(f"Test call created: {call['id']}")
408+
return call
409+
```
410+
</Tab>
411+
</Tabs>
412+
</Step>
413+
</Steps>
414+
415+
---
416+
32417
## Implementation Approaches
33418

34419
**Assistant-based implementation** uses transfer-type tools with conditions interpreted by the assistant through system prompts. The assistant determines when and where to route calls based on clearly defined tool purposes and routing logic in the prompt. Best for quick setup and simpler routing scenarios.

0 commit comments

Comments
 (0)