How to Implement Outbound Calling with LiveKit Agents: A Complete Guide

LiveKit Agents implement outbound calling by creating SIP participants through JobContext.add_sip_participant() and managing call flows with transfer_sip_participant() or the high-level WarmTransferTask workflow.

The livekit/agents repository provides a comprehensive framework for building voice AI agents that can initiate phone calls, transfer callers to human supervisors, and orchestrate complex telephony workflows. To implement outbound calling effectively, you need to understand the SIP participant management APIs in livekit-agents/livekit/agents/job.py and the warm transfer workflow in the beta workflows module.

Understanding the Core Components for Outbound Calling

Outbound calling in LiveKit Agents relies on two primary architectural components: the JobContext class for low-level SIP operations and the WarmTransferTask for high-level workflow orchestration.

JobContext and SIP Participant Management

The JobContext class, defined in livekit-agents/livekit/agents/job.py, exposes methods that interface directly with LiveKit's SIP service. When you implement outbound calling, you interact with two critical methods:

  • add_sip_participant() (lines 28-38): Creates a new SIP participant by dialing out through a configured trunk
  • transfer_sip_participant() (lines 71-84): Moves an existing SIP participant to a new destination

These methods handle the RTP stream establishment and SIP signaling required for telephony integration.

Creating Outbound Calls with add_sip_participant

To initiate an outbound call, use the add_sip_participant() method within your agent's job context. This method requires a valid outbound SIP trunk ID configured in your LiveKit project and a destination phone number or SIP URI.


# Inside an async function with a JobContext instance called `ctx`

sip_participant = await ctx.add_sip_participant(
    call_to="+18005551234",          # Destination phone number or sip:<user>@<host>

    trunk_id="ST_abc123",           # Outbound SIP trunk created in LiveKit

    participant_identity="outbound-call-1",
)

Once executed, the call appears as a RemoteParticipant in the current room. You can interact with this participant using standard room IO functions, including text-to-speech and speech-to-text pipelines.

Transferring Calls with transfer_sip_participant

After establishing an outbound call, you may need to redirect the participant to another destination. The transfer_sip_participant() method supports both cold transfers (immediate hand-off) and warm transfers (supervised hand-off).

Cold Transfers

A cold transfer immediately moves the call without intermediary interaction. Set play_dialtone=True to provide audio feedback during the transfer process.

await ctx.transfer_sip_participant(
    participant=sip_participant,    # RemoteParticipant or participant identity string

    transfer_to="+18009998877",    # New destination

    play_dialtone=True,
)

Implementing Warm Transfers with WarmTransferTask

For complex scenarios requiring human supervisor intervention, use the WarmTransferTask class located in livekit-agents/livekit/agents/beta/workflows/warm_transfer.py (lines 61-84). This high-level workflow orchestrates the complete warm transfer process:

  1. Dials a human supervisor using add_sip_participant
  2. Plays hold music or summary audio while connecting
  3. Exposes a connect_to_caller tool to the supervisor
  4. Merges the calls when the supervisor invokes the tool
from livekit.agents.beta.workflows import WarmTransferTask

result = await WarmTransferTask(
    target_phone_number="+18005550000",   # Supervisor number

    sip_trunk_id=os.getenv("LIVEKIT_SIP_OUTBOUND_TRUNK"),
    sip_number=os.getenv("LIVEKIT_SIP_NUMBER"),   # Caller-ID shown to supervisor

    chat_ctx=self.chat_ctx,              # Pass current LLM chat context for summarization

)

# `result.human_agent_identity` contains the SIP participant ID of the supervisor

The task automatically handles the SIP participant management internally, using the same JobContext methods described earlier.

Complete Implementation Example

The examples/warm-transfer/warm_transfer.py file (lines 21-74) demonstrates a production-ready implementation. This example creates a support agent that can transfer callers to human supervisors using the warm transfer workflow.

class SupportAgent(Agent):
    @function_tool
    async def transfer_to_human(self) -> None:
        await self.session.say("Please hold while I connect you to a human agent.")
        result = await WarmTransferTask(
            target_phone_number=os.getenv("LIVEKIT_SUPERVISOR_PHONE_NUMBER"),
            sip_trunk_id=os.getenv("LIVEKIT_SIP_OUTBOUND_TRUNK"),
            sip_number=os.getenv("LIVEKIT_SIP_NUMBER"),
            chat_ctx=self.chat_ctx,
        )
        await self.session.say(
            "You are now connected to my supervisor.", allow_interruptions=False
        )
        self.session.shutdown()

To run this example, configure the following environment variables:

  • LIVEKIT_SUPERVISOR_PHONE_NUMBER: The phone number to dial for human support
  • LIVEKIT_SIP_OUTBOUND_TRUNK: Your LiveKit SIP outbound trunk ID
  • LIVEKIT_SIP_NUMBER: The caller ID number displayed to the callee

Summary

  • Outbound calling in LiveKit Agents centers on the JobContext class in livekit-agents/livekit/agents/job.py, which provides add_sip_participant() for creating calls and transfer_sip_participant() for redirecting them.
  • Cold transfers immediately move callers to new destinations using the transfer method with optional dial-tone playback.
  • Warm transfers leverage the WarmTransferTask workflow in livekit-agents/livekit/agents/beta/workflows/warm_transfer.py to dial supervisors, play hold audio, and merge calls via the connect_to_caller tool.
  • Production implementations should reference the examples/warm-transfer/warm_transfer.py example for environment configuration and agent structure.

Frequently Asked Questions

What is the difference between cold and warm transfers in LiveKit?

A cold transfer uses JobContext.transfer_sip_participant() to immediately redirect an active SIP participant to a new phone number without intermediary interaction. A warm transfer uses the WarmTransferTask workflow to first dial a human supervisor, allow the supervisor to review a summary of the conversation, and then merge the calls only when the supervisor explicitly invokes the connect_to_caller tool.

How do I configure SIP trunking for outbound calling?

You must create an outbound SIP trunk in your LiveKit Cloud or self-hosted LiveKit server dashboard. Obtain the trunk ID (formatted as ST_xxxxxx) and pass it as the trunk_id parameter to JobContext.add_sip_participant() or as sip_trunk_id to WarmTransferTask. Additionally, set the sip_number parameter to control the caller ID displayed to the destination party.

Can I implement outbound calling without using WarmTransferTask?

Yes. You can implement outbound calling using only the low-level APIs in JobContext. Call add_sip_participant() to create the outbound call, then use standard room IO methods to interact with the resulting participant. If you need to redirect the call later, invoke transfer_sip_participant(). The WarmTransferTask is optional and provides convenience for complex supervisor hand-off workflows.

What environment variables are required for the warm transfer example?

The examples/warm-transfer/warm_transfer.py script requires three environment variables: LIVEKIT_SUPERVISOR_PHONE_NUMBER (the destination number for the human supervisor), LIVEKIT_SIP_OUTBOUND_TRUNK (your LiveKit SIP outbound trunk ID), and LIVEKIT_SIP_NUMBER (the caller ID number shown to the supervisor). These variables configure the SIP routing and identity for outbound calls initiated by the agent.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →