Skip to main content
This page follows the pipecat target of examples/salon-concierge, on the cloud-websocket transport: your own Twilio number, and nothing of yours hosted anywhere. Cold transfer is the only kind this route supports.
targets.yaml
connections/twilio_voice.yaml
agent.yaml
billing_line is a symbol, never a number: the right half names an environment variable read at call time, a rule Human transfers states in full. Your number points at a small piece of static markup in the Twilio console, which streams the call to Pipecat Cloud. No server of yours is in the path, in production or ever. The three names in the connection are here because this package hands calls to a person and places test calls, and both of those speak to Twilio’s API in your name. A package that only answers calls on this route needs no environment: block at all. Which keys each route accepts is in Connection configuration, and where each Twilio value is found is in Pipecat over Twilio.

The transfer

agent.yaml
The mechanism is different from every other route. One request replaces the live call’s instructions at Twilio, keyed on the call id: speak a line, dial the destination, done. The agent’s part of the call ends there. A successful Twilio REST update means the transfer has started, not that the destination answered. The tool result is transfer_started.

Every key this transfer takes

cold: is the only shape this route compiles, and on_unavailable: is the one key you cannot leave out. Pipecat cloud-websocket requires explicit on_unavailable: hangup; it cannot reconnect the original media stream. The entry’s own when:, and the choice of shape block, 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.
a positive Go duration, e.g. 25s
How long the destination rings before it counts as unavailable. It becomes the dial timeout in the markup Twilio runs. Left out, that timeout is 25 seconds.
hangup
required
What happens when the destination does not take the call. hangup is the only value this route accepts, and it has to be written out: an omitted key resolves to return_to_caller, which is refused here because this route cannot reconnect the original media stream.
There is no briefing: key to write. It belongs to a warm: block, and warm transfer compiles on no Pipecat route.

The transfer ends the call

After the destination leg ends, Twilio ends the original call. The same happens after a decline or no answer. Unmute does not reconnect the caller to a fresh agent, because that agent would have none of the conversation context. Unmute does not support warm transfer on any Pipecat target. Write one and validation refuses it, naming the connection and the transport it declares:
If you need warm transfer, use the LiveKit SIP route.

Testing it without waiting for a call

The package declares both directions:
agent.yaml
Outbound is there so you can call your own mobile and then ask the agent for billing, instead of waiting for someone to ring in.

Check the route compiles

A clean compile means the route is legal for this target and the cold transfer is one the route supports. The emitted README.md carries the Twilio markup the route needs.

Where to go next

Targets

Compare Pipecat, LiveKit, and SLNG.