You have a Telnyx Call Control app. Telnyx drives a call by webhook ping-pong:
  1. call.initiated arrives. You send answer.
  2. call.answered arrives. You send speak.
  3. call.speak.ended arrives. You send the next command.
Every step is a REST command keyed by call_control_id, with a webhook round-trip in between. Thus your app is a state machine, and every state transition crosses the network twice. On ClutchCall, the whole flow is one function. Your code runs as an external agent. The external agent is a worker. The worker dials one outbound QUIC/WebTransport session to the engine. It authenticates with a media app key/secret. It registers under an agent handle. The engine routes calls to the worker through a VENDOR_BRIDGE agent config. It hands each call to your coroutine. Audio flows in both directions as 20 ms pcm16 frames at 8 kHz, in-process, with no command round-trips.

What maps to what

Port the voicemail example

The runnable starter is a port of Telnyx’s voicemail-python Call Control example. It ships in the provider starters repo next to the telequick-agents SDK. Upstream, it is a Flask server plus a webhook state machine. Here, it is one coroutine:
1

Install the SDK

2

Collapse the state machine into a handler

agent.py
A return from the handler ends the call. For a durable recording, enable platform recording on the route. You then get a voice.recording.ready webhook with the file. You do not poll for call.recording.saved.
3

Create the external agent and route a number

The console’s External agents create flow (agent.clutchcall.dev) reserves the agent handle. It mints the media key/secret, which AgentConfig.from_env() reads. It writes the VENDOR_BRIDGE config. Route a DID to the agent. Then dial the DID.
4

Keep lifecycle webhooks — as observability

In Call Control, webhooks were the control flow. Here they only inform. Register one time with an mpk_ management key (console → Settings → API keys) against POST /mcp through the SDK wrapper:
Deliveries carry an X-Clutchcall-Signature header (t=<epoch>,v1=<hmac_sha256(secret, "t.body")>). Check each delivery with telequick_agents.webhooks.verify_signature before you trust the payload. Outbound dialing and transfer are voice.calls.originate / voice.calls.transfer on the same API.

Keep your Telnyx trunk

Numbers do not have to move first. Point a Telnyx SIP trunk at the platform as a carrier path. Telnyx keeps the numbers. ClutchCall terminates the SIP legs. The same external-agent routing applies. See SIP Trunking.

Troubleshooting

There is no call.speak.ended here. Nothing blocks on webhooks. Sequence your flow in the coroutine itself: send_audioflush → await playout. Then read call.audio().
send_audio is a raw passthrough at the call’s sample rate. Resample your prompt files to 8 kHz mono 16-bit PCM offline. Do not send wideband samples onto a telephony leg.
vendor_room in the agent config must exactly match the worker’s handle (TELEQUICK_AGENT). The DID must route to that agent. Also check outbound UDP/443. Media rides QUIC datagrams. A TCP-only network stalls audio after a successful registration.

Plivo integration

The same port for the answer-URL/XML model.

LiveKit integration

The external-agent seam with a full agent framework on top.

Run a LiveKit Agent on the Transport

The end-to-end external-agent recipe on the same seam.

SIP Trunking

Trunk setup, which includes how to point your Telnyx trunk at the platform.