examples/salon-concierge carries the cold half of this on its LiveKit target,
over a Twilio SIP trunk. Warm transfer compiles on no other route today, and no
shipped example declares it, so the warm snippets below are worked examples
rather than quotes from a package you can run.
On this page:
- Two transfers, side by side - the block, and every key it takes
- The trunk has to allow it - three Twilio settings a new trunk does not have
- The connection - the four SIP names this route reads
- Run it - compiling, deploying, and the lines the log prints
targets.yaml
agent.yaml
connections/twilio_sip.yaml, below. The two desks are in
agent.yaml, because the same desk answers whichever target places the call.
Each one names an environment variable rather than a number, a rule
Human transfers states in full.
Two transfers, side by side
agent.yaml
on_unavailable
applies.
Warm uses LiveKit’s own prebuilt warm transfer task. The caller waits on
hold, the supervisor’s line rings, the agent briefs them, and only then are the
two connected. briefing is free text, not a mode: the call transcript is
passed along on its own, so write what the person needs on top of it. Every
failure, no answer, decline, voicemail, failed dial, comes back as one error
and on_unavailable decides.
The person who answers hears the handover in the first sentence: who is on
hold, what they want, what was tried, then one question they can answer. If the
conversation has too little detail, the agent says that plainly instead of
inventing a briefing.
Every key a transfer block takes
destination: is the only key either block requires. The entry’s own when:,
and the choice between cold: and warm:, work the same on every route and
are stated on Human transfers.
a destinations entry name
required
Which desk to reach. The name is resolved in the
destinations: block above,
so the model never sees a number.string
What to tell the person while the caller waits. It belongs to a
warm: block
and is refused in a cold: one. Left out, the person still gets the
transcript, which rides along on its own.a positive Go duration, e.g. 25s
How long the destination rings before it counts as unavailable. It reaches
both shapes on this route. Left out, LiveKit is sent no value and its own
platform default stands.
What happens when the destination does not take the call. On this route no
answer, a decline, voicemail and a failed dial all arrive as one failure, so
one setting covers them.
ring_timeout covers ringing only. Once the person answers, LiveKit has no
post-answer timeout and the caller stays on hold until the consultation ends.
The agent asks again when the person does not decide, but that prompt is a
mitigation rather than a hard time limit.
The trunk has to allow it
Cold transfer is a SIP REFER, so the trunk must permit transfers. In the trunk’s General settings, three things have to be right, and a new trunk has none of them:
Twilio’s transfer mode has a third value,
sip-only, which allows SIP
destinations and refuses PSTN ones. A phone number destination needs the PSTN
box ticked.
Caller ID decides whether the transfer connects at all. Transferee presents
the original caller’s number to the destination, which is the nicer behaviour
and works where the receiving carrier accepts a number your account does not
own. Transferor presents your own Twilio number, and is what to use when the
transfer crosses a border. Getting this wrong fails every transfer instantly,
with 486 Busy Here and a zero second, zero cost call leg in Twilio’s log.
The caller ID section shows
how to tell that apart from a destination that is genuinely busy.
Caller ID for the transfer target is that trunk setting, never per call.
Transfers to emergency numbers are not supported, and the referred leg keeps
billing per minute trunking charges.
LiveKit Phone Numbers cannot transfer. Use a SIP trunk whose provider supports
REFER; this example uses Twilio Elastic SIP Trunking.
The connection
Four standard SIP names, not Twilio specific ones, because the same generated code dials through any SIP carrier with them:connections/twilio_sip.yaml
Run it
README.md walks the trunk
setup for it.
Inbound on this route is tested against a deployment: SIP needs the carrier to
reach signalling and media at a routable address, which a laptop behind a home
router cannot offer. Deploy the generated project, finish the trunk setup
above, and call your number to reach a transfer.
Cold transfer needs an existing SIP caller leg, so it cannot be tested from a
browser session or the LiveKit Agent Console. Warm transfer can start there,
because it dials the person itself.
That difference also controls startup checks. A cold destination environment
name is required only after a real SIP job is identified, before the greeting;
it does not block WebRTC startup. Warm transfer is always available to the
browser agent, so its destination and the selected connection’s SIP address,
username, password, and caller number remain required in REQUIRED_ENV and
compose.dev.yaml.
Keep lk agent logs open. A warm transfer prints the control, the conversation
message count handed to the briefing, and then either warm transfer merged
or warm transfer unavailable. A cold transfer prints either
cold transfer completed, cold transfer failed, or
cold transfer skipped: no phone caller in the room. A dial line with no final
warm line means the consultation is still running.
Where to go next
Pipecat on Twilio
Cold transfer on Pipecat with nothing hosted by you.
Targets
Compare Pipecat, LiveKit, and SLNG.