A cold transfer refers to forwarding a caller to another phone number or SIP endpoint. Performing a cold transfer closes the caller’s LiveKit session.
For transfers that include an AI agent to provide context, see the Agent-assisted transfer guide.
How it works
To transfer a caller out of a LiveKit room to another phone number, use the following steps:
- Call the
TransferSIPParticipantAPI. - LiveKit sends a SIP REFER through your trunk, instructing the provider to connect the caller to the new number or SIP endpoint.
- The caller leaves the LiveKit room, ending the session.
Transferring a SIP participant using SIP REFER
REFER is a SIP method that allows you to move an active session to another endpoint (that is, transfer a call). For LiveKit telephony apps, you can use the TransferSIPParticipant server API to transfer a caller to another phone number or SIP endpoint.
In order to successfully transfer calls, you must configure your provider trunks to allow call transfers.
Enable call transfers for your Twilio SIP trunk
Enable call transfer and PSTN transfers for your Twilio SIP trunk. To learn more, see Twilio's Call Transfer via SIP REFER documentation.
When you transfer a call, you have the option to set the caller ID to display the phone number of the transferee (the caller) or the transferor (the phone number associated with your LiveKit trunk).
The following command enables call transfers and sets the caller ID to display the number of the transferee:
- To list trunks, execute
twilio api trunking v1 trunks list. - To set the caller ID to the transferor, set
transfer-caller-idtofrom-transferor.
twilio api trunking v1 trunks update --sid <twilio-trunk-sid> \--transfer-mode enable-all \--transfer-caller-id from-transferee
- Sign in to the Twilio console.
- Navigate to Elastic SIP Trunking » Manage » Trunks, and select a trunk.
- In the Features » Call Transfer (SIP REFER) section, select Enabled.
- In the Caller ID for Transfer Target field, select an option.
- Select Enable PSTN Transfer.
- Save your changes.
Usage
Set up the following environment variables:
export LIVEKIT_URL=<your LiveKit server URL>export LIVEKIT_API_KEY=<your API Key>export LIVEKIT_API_SECRET=<your API Secret>
This example uses the LiveKit URL, API key, and secret set as environment variables.
import { SipClient } from 'livekit-server-sdk';// ...async function transferParticipant(participant) {console.log("transfer participant initiated");const sipTransferOptions = {playDialtone: false};const sipClient = new SipClient(process.env.LIVEKIT_URL,process.env.LIVEKIT_API_KEY,process.env.LIVEKIT_API_SECRET);const transferTo = "tel:+15105550100";try {await sipClient.transferSipParticipant('open-room', participant.identity, transferTo, sipTransferOptions);console.log("SIP participant transferred successfully");} catch (error) {if (error instanceof TwirpError && error.metadata != null) {console.error("SIP error code: ", error.metadata?.['sip_status_code']);console.error("SIP error message: ", error.metadata?.['sip_status']);} else {console.error("Error transferring SIP participant: ", error);}}}
import asyncioimport loggingimport osfrom livekit import apifrom livekit.protocol.sip import TransferSIPParticipantRequestlogger = logging.getLogger("transfer-logger")logger.setLevel(logging.INFO)async def transfer_call(participant_identity: str, room_name: str) -> None:async with api.LiveKitAPI() as livekit_api:transfer_to = 'tel:+14155550100'try:# Create transfer requesttransfer_request = TransferSIPParticipantRequest(participant_identity=participant_identity,room_name=room_name,transfer_to=transfer_to,play_dialtone=False)logger.debug(f"Transfer request: {transfer_request}")# Transfer callerawait livekit_api.sip.transfer_sip_participant(transfer_request)print("SIP participant transferred successfully")except Exception as error:# Check if it's a Twirp error with metadataif hasattr(error, 'metadata') and error.metadata:print(f"SIP error code: {error.metadata.get('sip_status_code')}")print(f"SIP error message: {error.metadata.get('sip_status')}")else:print(f"Error transferring SIP participant:")print(f"{error.status} - {error.code} - {error.message}")
For a full example using a voice agent, DTMF, and SIP REFER, see the phone assistant example.
require 'livekit'room_name = 'open-room'participant_identity = 'participant_identity'def transferParticipant(room_name, participant_identity)sip_service = LiveKit::SIPServiceClient.new(ENV['LIVEKIT_URL'],api_key: ENV['LIVEKIT_API_KEY'],api_secret: ENV['LIVEKIT_API_SECRET'])transfer_to = 'tel:+14155550100'response = sip_service.transfer_sip_participant(room_name,participant_identity,transfer_to,play_dialtone: false)if response.error thenputs "Error: #{response.error}"elseputs "SIP participant transferred successfully"endend
import ("context""fmt""os""github.com/livekit/protocol/livekit"lksdk "github.com/livekit/server-sdk-go/v2")func transferParticipant(ctx context.Context, participantIdentity string) {fmt.Println("Starting SIP participant transfer...")roomName := "open-room"transferTo := "tel:+14155550100"// Create a transfer requesttransferRequest := &livekit.TransferSIPParticipantRequest{RoomName: roomName,ParticipantIdentity: participantIdentity,TransferTo: transferTo,PlayDialtone: false,}fmt.Println("Creating SIP client...")sipClient := lksdk.NewSIPClient(os.Getenv("LIVEKIT_URL"),os.Getenv("LIVEKIT_API_KEY"),os.Getenv("LIVEKIT_API_SECRET"))// Execute transfer requestfmt.Println("Executing transfer request...")_, err := sipClient.TransferSIPParticipant(ctx, transferRequest)if err != nil {fmt.Println("Error:", err)return}fmt.Println("SIP participant transferred successfully")}
lk sip participant transfer --room <CURRENT_ROOM> \--identity <PARTICIPANT_ID> \--to "<SIP_ENDPOINT>"
Where <SIP_ENDPOINT> is a valid SIP endpoint or telephone number. The following examples are valid formats:
tel:+15105550100sip:+15105550100@sip.telnyx.comsip:+15105550100@my-livekit-demo.pstn.twilio.com
Forward calls with an agent tool
Your agent can use the TransferSIPParticipant API to transfer calls to without staying on the line. The current session ends after the transfer is complete. The following example shows how to define a tool in your agent class that calls TransferSIPParticipant.
class Assistant(Agent):## ... existing init code ...@function_tool()async def transfer_call(self, ctx: RunContext):"""Transfer the call to a human agent, called after confirming with the user"""transfer_to = "+15105550123"participant_identity = "+15105550123"# let the message play fully before transferringawait ctx.session.generate_reply(instructions="Inform the user that you're transferring them to a different agent.")job_ctx = get_job_context()try:await job_ctx.api.sip.transfer_sip_participant(api.TransferSIPParticipantRequest(room_name=job_ctx.room.name,participant_identity=participant_identity,# to use a sip destination, use `sip:user@host` formattransfer_to=f"tel:{transfer_to}",))except Exception as e:print(f"error transferring call: {e}")# give the LLM that contextreturn "could not transfer call"
To use the Node.js example, you must install the @livekit/rtc-node package:
pnpm add @livekit/rtc-node
// Add these imports at the top of your fileimport { SipClient } from 'livekit-server-sdk';import { RemoteParticipant } from '@livekit/rtc-node';// Add this function definitionasync function transferParticipant(participant: RemoteParticipant, roomName: string) {console.log("transfer participant initiated for participant: ", participant.identity);const sipTransferOptions = {playDialtone: false};const sipClient = new SipClient(process.env.LIVEKIT_URL!,process.env.LIVEKIT_API_KEY!,process.env.LIVEKIT_API_SECRET!);const transferTo = "tel:+15105550123";await sipClient.transferSipParticipant(roomName, participant.identity, transferTo, sipTransferOptions);console.log('transferred participant');}
Additional resources
The following guides provide more information on building voice agents for telephony.
Agent-assisted warm transfer
Transfer calls with agent assistance and context.
Tool definition & use
Extend your agent's capabilities with tools.
Workflows
Orchestrate detailed workflows such as collecting credit card information over the phone.
Agent speech
Customize and perfect your agent's verbal interactions.