#Call forward
1 messages · Page 1 of 1 (latest)
I'd love to have the call forwarding/transfer ability.
The idea is to bridge an ongoing call with an AI agent to a human agent.
BTW, I solved this problem by writing some code on top of the boilerplate from the docs.
I setup a client tool calling for the agent on 11labs dashboard with the name "transfer_to_human"
console.log("[ElevenLabs] Received client tool call:", message.client_tool_call);
if (message.client_tool_call?.tool_name === "transfer_to_human") {
try {
await handleTransfer();
// Send success response
elevenLabsWs.send(JSON.stringify({
type: "client_tool_result",
tool_call_id: message.client_tool_call.tool_call_id,
result: "Transfer initiated successfully",
is_error: false
}));
// Don't close immediately - let Twilio handle the transfer
} catch (error) {
console.error("[ElevenLabs] Transfer failed:", error);
// Send error response
elevenLabsWs.send(JSON.stringify({
type: "client_tool_result",
tool_call_id: message.client_tool_call.tool_call_id,
result: error.message,
is_error: true
}));
}
}
break;
I added this case in the elevenLabsWs.on("message") handler
This is how I setup the handleTransfer() function:
if (!HUMAN_AGENT_PHONE_NUMBER) {
console.error("[Transfer] Cannot transfer: HUMAN_AGENT_PHONE_NUMBER not configured");
throw new Error("Transfer not available - no agent number configured");
}
if (!callSid) {
console.error("[Transfer] Cannot transfer: No active call");
throw new Error("No active call to transfer");
}
// First, notify Twilio WebSocket about the transfer
console.log("[Transfer] Notifying Twilio about transfer");
ws.send(JSON.stringify({
event: "transfer",
streamSid,
transferTarget: HUMAN_AGENT_PHONE_NUMBER
}));
// Then update the call with new TwiML
const twiml = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please hold while I transfer you to a human agent.</Say>
<Dial>${HUMAN_AGENT_PHONE_NUMBER}</Dial>
</Response>`;
console.log(`[Transfer] Transferring call ${callSid} to ${HUMAN_AGENT_PHONE_NUMBER}`);
await twilioClient.calls(callSid)
.update({
twiml: twiml
});
console.log("[Transfer] Transfer successful");
}
To handle the call transfer from Twilio:
case "transfer":
console.log("[Twilio] Received transfer event:", msg);
if (HUMAN_AGENT_PHONE_NUMBER) {
if (msg.transferTarget === HUMAN_AGENT_PHONE_NUMBER) {
console.log(`[Twilio] Transferring call ${callSid} to human agent at ${HUMAN_AGENT_PHONE_NUMBER}`);
try {
const twiml = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please hold while I transfer you to a human agent.</Say>
<Dial>${HUMAN_AGENT_PHONE_NUMBER}</Dial>
</Response>`;
await twilioClient.calls(callSid)
.update({
twiml: twiml
});
// Send transfer-complete event
ws.send(JSON.stringify({
event: "transfer-complete",
streamSid
}));
} catch (error) {
console.error("[Twilio] Error transferring call:", {
message: error.message,
code: error.code,
status: error.status,
moreInfo: error.moreInfo
});
}
} else {
console.error("[Twilio] Transfer target mismatch:", {
expected: HUMAN_AGENT_PHONE_NUMBER,
received: msg.transferTarget
});
}
} else {
console.error("[Twilio] Cannot transfer: HUMAN_AGENT_PHONE_NUMBER not configured");
}
break;
And when the transfer is completed, I'm closing the connection to ElevenLabs.
console.log("[Twilio] Transfer completed, closing ElevenLabs connection");
if (elevenLabsWs?.readyState === WebSocket.OPEN) {
elevenLabsWs.close();
}
break;
I think the next checkpoint would be how to transfer context/data from the call to the human agent before they take over.
Hey, did you ever get this working?