Skip to main content
This is the cloud-websocket transport with the twilio carrier. Your number points at a static piece of markup in the Twilio console, and that markup streams the call straight to Pipecat Cloud, which starts your agent.
This route is not SIP. It shares nothing with the LiveKit Twilio route except the account the number lives in. There is no trunk, no webhook of yours, no tunnel, and no public URL.
Your own build/pipecat/README.md is the authority for your build, because it was generated with your region and your agent name already filled in. This page is the same route with the reasons attached, and it is the page to read before the first call rather than after it.
On this page:

Nothing of yours is hosted, and that decides the security model

The generated pcc-deploy.toml sets websocket_auth = "none". That is not a shortcut. A TwiML Bin is static markup in a console, so it cannot fetch a token before it opens the stream, which makes token authentication structurally impossible on this route. What limits who can start a session is knowing the AGENT.ORGANISATION string your markup carries. Treat that string like a capability. It is not a secret in the cryptographic sense, but anyone holding it can open sessions you pay for.

The values that have to agree

The route fails when any of these disagree, and the failures look nothing like each other.

The stream address carries the region

A regional stream endpoint routes only to agents deployed in that region. Your build writes the right one, because it comes from deployment_region on the target:
targets.yaml
One declaration, three places, and the platform needs all three to match: An agent can only read a secret set from its own region. To move region, change that one line, recompile, and paste the new address into the Bin. See Pipecat’s regions guide for what each region name covers.

The service host is your agent name and your organisation slug

_pipecatCloudServiceHost is AGENT_NAME.ORGANIZATION_NAME. The agent name is filled in for you; the organisation is the one value the compiler cannot know:
You want the machine slug, not your display name. It looks like three-random-words-12345: lowercase, hyphenated, ending in a number. Column headings differ between CLI versions, so go by the shape of the value. It never carries the (active) marker.
This is the most common way the route fails, and the least helpful failure you can get. A service host the platform refuses is rejected before it reaches your agent, so your agent’s log stays completely empty. The caller hears the spoken line and then silence. Pipecat’s own Twilio websocket guide documents the same markup shape.

The markup carries only the service host

A <Parameter> reaches this agent only if the emitted code reads one by that name, and on an inbound call that name is _pipecatCloudServiceHost alone. Adding the caller’s or the called number here is a common suggestion, and it does nothing: no module in this package reads it.

The number must not be on a SIP trunk

A number attached to a trunk ignores its voice configuration, silently, so the Bin is never consulted. Read the state back rather than testing by ear:
An empty trunkSid is what you want. One number serves one target at a time, so buy a second number if you want both routes live.

The markup

Paste this from your own build/pipecat/README.md, where the address and the agent name are already correct. YOUR_ORGANIZATION is the only value you fill in by hand.
Console path: TwiML, TwiML Bins, then the plus button. Name it anything. Then point the number at it: Phone Numbers, Manage, Active Numbers, your number, Voice Configuration, “A call comes in”, choose TwiML Bin. The <Say> line is a cold start cushion, not a requirement. Starting this agent from cold takes a few seconds, and a caller who hears nothing hangs up. Drop the line once you keep a warm instance.

What the package declares for this route

Two files carry this route: the connection that names it, and the target that names the connection. The connection is the short one.
connections/twilio_voice.yaml

Every key a connection takes

sip | connector | cloud-websocket | daily-sip
required
The mechanism that carries the call. cloud-websocket is this route, and it is the one where Pipecat Cloud terminates the carrier’s stream itself.
twilio
required
The carrier account behind the route. This route has no other carrier.
route key to environment variable name
Left out entirely on a package that only answers calls, which is what makes the file above two lines long. A package that places a call, or hands one to a person, needs account_sid, auth_token and from_number here, and the refusal says which behaviour asked for them.

Every key a target takes

The target is the block under Deploy with a warm instance. These are its keys.
livekit | pipecat | slng
required
pipecat on this route.
exact x.y.z
required
The framework version pinned into the emitted project, written with all three numbers. targets.yaml names the one this release supports.
connection file stem
Which connection carries this target’s calls, named without the folder and without the .yaml. Required for a phone agent.
platform region name
Where the platform deploys the agent. Pipecat takes exactly one, and it is also the region in your stream address.
integer
How many instances the platform holds ready. On this route it is what makes the call answerable: see below.
pins, sdk_language and per-target models: are the remaining target keys, and targets.yaml covers all three.

Deploy with a warm instance

targets.yaml
On this route a warm instance is not a latency nicety, it is what makes the call answerable. A cold container can take longer to start than a session stays open, so the session expires before the container is ready and the call is never picked up at all. A knowledge base makes this more likely, because the corpus is embedded at import, before the server binds. warm_instances compiles to [scaling] min_agents in pcc-deploy.toml, so every deploy of this build keeps the pool. It bills for that instance whether or not anybody calls, which is why the compiler never adds it on its own.
pipecat cloud deploy --min-agents 1 is the same thing for one deploy only, and a [scaling] block added to pcc-deploy.toml by hand does not survive the next unmute compile. Declare it in targets.yaml and the manifest carries it.

Three different ways nothing happens

They look identical to the caller and they are easy to tell apart in the logs. Only the first one is a wiring problem. The second is warm_instances. The third is a secret that is declared but not in the set you deployed with.

Reading the agent’s log

-l DEBUG filters to only DEBUG. It is not a minimum severity, so it hides the ERROR line that says why the session died. Run with no -l at all.
The CLI wraps output at 80 columns, which corrupts JSON output. For machine reading:
Two lines are worth knowing by name:
  • parse_telephony_websocket ... Parsed - Type: twilio, Data: {...} proves the Bin delivered, and shows the service host and the parameters it carried.
  • Generating chat from context [...] dumps what the model actually saw. It is the fastest way to spot echo, fragmentation, or a polluted context.

Speakerphone makes the agent interrupt itself

A phone leg has no echo cancellation. A caller on speakerphone sends the agent’s own greeting back into the microphone, it is transcribed as caller speech, and the agent cuts itself off and carries the garbled turn in its context for the rest of the call. A Pipecat phone route protects the greeting by default for exactly this reason. Everything after the opening line stays interruptible, so tell testers to use the handset. See interruption.protect to change what is protected.

Where to go next

Human transfers

Cold and warm, and what each route can do.

Transfers on this route

Hand the caller to a person.