Call Transfer

View as MarkdownOpen in Claude

Call transfer is essential for agents that need to escalate to humans, route to specialized departments, or hand off to other AI agents. The SDK provides multiple transfer mechanisms, each suited to different scenarios.

Understanding the difference between these methods — and when to use each — helps you build agents that route calls efficiently while maintaining a good caller experience.

Choosing a Transfer Method

The SDK offers several ways to transfer calls. Here’s how to choose:

MethodBest ForDestinationWhat Happens
connect()Phone numbers, SIPPSTN, SIP endpointsDirect telephony connection
swml_transfer()Other AI agentsSWML URLsHand off to another agent
sip_refer()SIP environmentsSIP URIsSIP REFER signaling

Transfer methods across all languages:

Languageconnect()swml_transfer()
Pythonresult.connect("+15551234567", final=True)result.swml_transfer(dest="https://...", final=True)
TypeScriptresult.connect('+15551234567', { final: true })result.swmlTransfer({ dest: 'https://...', final: true })

Use connect() when:

  • Transferring to a phone number (human agents, call centers)
  • Connecting to SIP endpoints on your PBX
  • You need caller ID control on the outbound leg

Use swml_transfer() when:

  • Handing off to another AI agent
  • The destination is a SWML endpoint
  • You want the call to continue with different agent logic

Use sip_refer() when:

  • Your infrastructure uses SIP REFER for transfers
  • Integrating with traditional telephony systems that expect REFER

Transfer Types

Permanent Transfer (final=True)

  • Call exits the agent completely
  • Caller connected directly to destination
  • Agent conversation ends
  • Use for: Handoff to human, transfer to another system

Temporary Transfer (final=False)

  • Call returns to agent when far end hangs up
  • Agent can continue conversation after transfer
  • Use for: Conferencing, brief consultations

Basic Phone Transfer

1from signalwire import AgentBase
2from signalwire.core.function_result import FunctionResult
3
4class TransferAgent(AgentBase):
5 def __init__(self):
6 super().__init__(name="transfer-agent")
7 self.add_language("English", "en-US", "rime.spore")
8
9 self.prompt_add_section(
10 "Role",
11 "You are a receptionist who can transfer calls to different departments."
12 )
13
14 self.define_tool(
15 name="transfer_to_sales",
16 description="Transfer the caller to the sales department",
17 parameters={"type": "object", "properties": {}},
18 handler=self.transfer_to_sales
19 )
20
21 def transfer_to_sales(self, args, raw_data):
22 return (
23 FunctionResult("Transferring you to sales now.")
24 .connect("+15551234567", final=True)
25 )
26
27if __name__ == "__main__":
28 agent = TransferAgent()
29 agent.run()

Connect Method Parameters

ParameterTypeDefaultDescription
destinationstrrequiredPhone number, SIP address, or URI
finalboolTruePermanent (True) or temporary (False)
from_addrstrNoneOverride caller ID for outbound leg

Permanent vs Temporary Transfer

1from signalwire import AgentBase
2from signalwire.core.function_result import FunctionResult
3
4class SmartTransferAgent(AgentBase):
5 def __init__(self):
6 super().__init__(name="smart-transfer-agent")
7 self.add_language("English", "en-US", "rime.spore")
8
9 self.prompt_add_section(
10 "Role",
11 "You can transfer calls permanently or temporarily."
12 )
13
14 self._register_functions()
15
16 def _register_functions(self):
17 self.define_tool(
18 name="transfer_permanent",
19 description="Permanently transfer to support (call ends with agent)",
20 parameters={
21 "type": "object",
22 "properties": {
23 "number": {"type": "string", "description": "Phone number"}
24 },
25 "required": ["number"]
26 },
27 handler=self.transfer_permanent
28 )
29
30 self.define_tool(
31 name="transfer_temporary",
32 description="Temporarily connect to expert, then return to agent",
33 parameters={
34 "type": "object",
35 "properties": {
36 "number": {"type": "string", "description": "Phone number"}
37 },
38 "required": ["number"]
39 },
40 handler=self.transfer_temporary
41 )
42
43 def transfer_permanent(self, args, raw_data):
44 number = args.get("number")
45 return (
46 FunctionResult(f"Transferring you now. Goodbye!")
47 .connect(number, final=True)
48 )
49
50 def transfer_temporary(self, args, raw_data):
51 number = args.get("number")
52 return (
53 FunctionResult("Connecting you briefly. I'll be here when you're done.")
54 .connect(number, final=False)
55 )
56
57if __name__ == "__main__":
58 agent = SmartTransferAgent()
59 agent.run()

SIP Transfer

Transfer to SIP endpoints:

1def transfer_to_sip(self, args, raw_data):
2 return (
3 FunctionResult("Connecting to internal support")
4 .connect("sip:support@company.com", final=True)
5 )

Transfer with Caller ID Override

1def transfer_with_custom_callerid(self, args, raw_data):
2 return (
3 FunctionResult("Connecting you now")
4 .connect(
5 "+15551234567",
6 final=True,
7 from_addr="+15559876543" # Custom caller ID
8 )
9 )

SWML Transfer

Transfer to another SWML endpoint (another agent):

1from signalwire import AgentBase
2from signalwire.core.function_result import FunctionResult
3
4class MultiAgentTransfer(AgentBase):
5 def __init__(self):
6 super().__init__(name="multi-agent-transfer")
7 self.add_language("English", "en-US", "rime.spore")
8
9 self.prompt_add_section("Role", "You route calls to specialized agents.")
10
11 self.define_tool(
12 name="transfer_to_billing",
13 description="Transfer to the billing specialist agent",
14 parameters={"type": "object", "properties": {}},
15 handler=self.transfer_to_billing
16 )
17
18 def transfer_to_billing(self, args, raw_data):
19 return (
20 FunctionResult(
21 "I'm transferring you to our billing specialist.",
22 post_process=True # Speak message before transfer
23 )
24 .swml_transfer(
25 dest="https://agents.example.com/billing",
26 ai_response="How else can I help?", # Used if final=False
27 final=True
28 )
29 )
30
31if __name__ == "__main__":
32 agent = MultiAgentTransfer()
33 agent.run()

Transfer Flow

Diagram showing the flow of permanent and temporary call transfers between agents and destinations.
Call transfer flow showing permanent and temporary transfer paths.

Department Transfer Example

1from signalwire import AgentBase
2from signalwire.core.function_result import FunctionResult
3
4class ReceptionistAgent(AgentBase):
5 """Receptionist that routes calls to departments"""
6
7 DEPARTMENTS = {
8 "sales": "+15551111111",
9 "support": "+15552222222",
10 "billing": "+15553333333",
11 "hr": "+15554444444"
12 }
13
14 def __init__(self):
15 super().__init__(name="receptionist-agent")
16 self.add_language("English", "en-US", "rime.spore")
17
18 self.prompt_add_section(
19 "Role",
20 "You are the company receptionist. Help callers reach the right department."
21 )
22
23 self.prompt_add_section(
24 "Available Departments",
25 "Sales, Support, Billing, Human Resources (HR)"
26 )
27
28 self.define_tool(
29 name="transfer_to_department",
30 description="Transfer caller to a specific department",
31 parameters={
32 "type": "object",
33 "properties": {
34 "department": {
35 "type": "string",
36 "description": "Department name",
37 "enum": ["sales", "support", "billing", "hr"]
38 }
39 },
40 "required": ["department"]
41 },
42 handler=self.transfer_to_department
43 )
44
45 def transfer_to_department(self, args, raw_data):
46 dept = args.get("department", "").lower()
47
48 if dept not in self.DEPARTMENTS:
49 return FunctionResult(
50 f"I don't recognize the department '{dept}'. "
51 "Available departments are: Sales, Support, Billing, and HR."
52 )
53
54 number = self.DEPARTMENTS[dept]
55 dept_name = dept.upper() if dept == "hr" else dept.capitalize()
56
57 return (
58 FunctionResult(f"Transferring you to {dept_name} now. Have a great day!")
59 .connect(number, final=True)
60 )
61
62if __name__ == "__main__":
63 agent = ReceptionistAgent()
64 agent.run()

Sending SMS During Transfer

Notify the user via SMS before transfer:

1def transfer_with_sms(self, args, raw_data):
2 caller_number = raw_data.get("caller_id_number")
3
4 return (
5 FunctionResult("I'm transferring you and sending a confirmation text.")
6 .send_sms(
7 to_number=caller_number,
8 from_number="+15559876543",
9 body="You're being transferred to our support team. Reference #12345"
10 )
11 .connect("+15551234567", final=True)
12 )

Post-Process Transfer

Use post_process=True to have the AI speak before executing the transfer:

1def announced_transfer(self, args, raw_data):
2 return (
3 FunctionResult(
4 "Please hold while I transfer you to our specialist. "
5 "This should only take a moment.",
6 post_process=True # AI speaks this before transfer executes
7 )
8 .connect("+15551234567", final=True)
9 )

Warm vs Cold Transfers

Understanding the difference between warm and cold transfers helps you design better caller experiences.

Cold Transfer (Blind Transfer)

The caller is connected to the destination without any preparation. The destination answers not knowing who’s calling or why.

1def cold_transfer(self, args, raw_data):
2 return (
3 FunctionResult("Transferring you to support now.")
4 .connect("+15551234567", final=True)
5 )

When to use cold transfers:

  • High-volume call centers where speed matters
  • After-hours routing to voicemail
  • Simple department routing where context isn’t needed
  • When the destination has caller ID and can look up the caller

Warm Transfer (Announced Transfer)

The agent announces the transfer and potentially provides context before connecting. With AI agents, this typically means:

  1. Informing the caller about the transfer
  2. Optionally sending context to the destination
  3. Then executing the transfer
1def warm_transfer_with_context(self, args, raw_data):
2 caller_number = raw_data.get("caller_id_number")
3 call_summary = "Caller needs help with billing dispute"
4
5 return (
6 FunctionResult(
7 "I'm transferring you to our billing specialist. "
8 "I'll let them know about your situation.",
9 post_process=True
10 )
11 .send_sms(
12 to_number="+15551234567",
13 from_number="+15559876543",
14 body=f"Incoming transfer: {caller_number}\n{call_summary}"
15 )
16 .connect("+15551234567", final=True)
17 )

When to use warm transfers:

  • Escalations where context improves resolution
  • VIP callers who expect personalized service
  • Complex issues that need explanation
  • When you want to improve first-call resolution

Handling Transfer Failures

Transfers can fail for various reasons: busy lines, no answer, invalid numbers. Plan for these scenarios.

Validating Before Transfer

1def transfer_to_department(self, args, raw_data):
2 dept = args.get("department", "").lower()
3
4 DEPARTMENTS = {
5 "sales": "+15551111111",
6 "support": "+15552222222",
7 }
8
9 if dept not in DEPARTMENTS:
10 return FunctionResult(
11 f"I don't have a number for '{dept}'. "
12 "I can transfer you to Sales or Support."
13 )
14
15 return (
16 FunctionResult(f"Transferring to {dept}.")
17 .connect(DEPARTMENTS[dept], final=True)
18 )

Fallback Strategies

For temporary transfers (final=False), you can handle what happens when the transfer fails or the far end hangs up:

1def consultation_transfer(self, args, raw_data):
2 return (
3 FunctionResult(
4 "Let me connect you with a specialist briefly."
5 )
6 .connect(
7 "+15551234567",
8 final=False # Call returns to agent if transfer fails or ends
9 )
10 )

Transfer Patterns

Escalation to Human

1def escalate_to_human(self, args, raw_data):
2 reason = args.get("reason", "Customer requested")
3 self.log.info(f"Escalating call: {reason}")
4
5 return (
6 FunctionResult(
7 "I understand you'd like to speak with a person. "
8 "Let me transfer you to one of our team members.",
9 post_process=True
10 )
11 .connect("+15551234567", final=True)
12 )

Queue-Based Routing

1def route_to_queue(self, args, raw_data):
2 issue_type = args.get("issue_type", "general")
3
4 QUEUES = {
5 "billing": "+15551111111",
6 "technical": "+15552222222",
7 "sales": "+15553333333",
8 "general": "+15554444444"
9 }
10
11 queue_number = QUEUES.get(issue_type, QUEUES["general"])
12
13 return (
14 FunctionResult(f"Routing you to our {issue_type} team.")
15 .connect(queue_number, final=True)
16 )

Agent-to-Agent Handoff

1def handoff_to_specialist(self, args, raw_data):
2 specialty = args.get("specialty")
3
4 SPECIALIST_AGENTS = {
5 "billing": "https://agents.example.com/billing",
6 "technical": "https://agents.example.com/technical",
7 "sales": "https://agents.example.com/sales"
8 }
9
10 if specialty not in SPECIALIST_AGENTS:
11 return FunctionResult(
12 "I don't have a specialist for that area. Let me help you directly."
13 )
14
15 return (
16 FunctionResult(
17 f"I'm connecting you with our {specialty} specialist.",
18 post_process=True
19 )
20 .swml_transfer(
21 dest=SPECIALIST_AGENTS[specialty],
22 final=True
23 )
24 )

Transfer Methods Summary

MethodUse CaseDestination Types
connect()Direct call transferPhone numbers, SIP URIs
swml_transfer()Transfer to another agentSWML endpoint URLs
sip_refer()SIP-based transferSIP URIs

Best Practices

DO:

  • Use post_process=True to announce transfers
  • Validate destination numbers before transfer
  • Log transfers for tracking and compliance
  • Use final=False for consultation/return flows
  • Provide clear confirmation to the caller
  • Send context to the destination when helpful
  • Have fallback options if transfer fails

DON’T:

  • Transfer without informing the caller
  • Use hard-coded numbers without validation
  • Forget to handle transfer failures gracefully
  • Use final=True when you need the call to return
  • Transfer to unverified or potentially invalid destinations