VENDOR_BRIDGE agent config. It hands each call to your process as a Call
object. Audio flows in both directions as 20 ms pcm16 frames at 8 kHz.
The biggest practical difference is the connection direction. Twilio’s cloud
calls into your server. Thus you need a public URL, and usually ngrok
during development. Here the worker dials out. There are no inbound ports,
no public URL, and no tunnel.
What maps to what
Port the speech-assistant sample
The runnable starter is a port of Twilio’sspeech-assistant-openai-realtime-api-python sample. The OpenAI Realtime
bridge is the upstream file’s code, near-verbatim. That bridge covers session
setup, μ-law audio in both directions, and barge-in via
conversation.item.truncate. The FastAPI app, TwiML route, and WebSocket
plumbing are gone. The starter ships in the provider starters repo next to
the telequick-agents SDK.
1
Install the SDK
2
Replace the WebSocket handler with a call handler
The Twilio sample handles You can send outbound audio in any chunk size. The SDK paces it onto the
wire as steady 20 ms frames. Thus bursty Realtime deltas need no
re-chunking on your side.
"start" / "media" / "mark" JSON events on
a WebSocket. The port is a plain coroutine for each call:main.py
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
(TELEQUICK_MEDIA_KEY / TELEQUICK_MEDIA_SECRET, which
AgentConfig.from_env() reads). It writes the VENDOR_BRIDGE config that
routes calls to the handle. Attach the agent to a DID or trunk (see
SIP Trunking). Then
dial.4
Originate calls without an answer URL
Twilio’s
calls.create takes a TwiML url to control the answered call.
Here you pass the agent instead. On answer, the engine connects the
callee to your worker. Management operations use an mpk_ API key
(console → Settings → API keys) against POST /mcp. The SDK wraps that
endpoint:voice.calls.transfer and voice.calls.hangup cover mid-call control in
the same way.Webhooks instead of status callbacks
Register one endpoint for the voice vertical and select your events:X-Clutchcall-Signature header of the
form t=<epoch>,v1=<hmac_sha256(secret, "t.body")>. Check each delivery with
telequick_agents.webhooks.verify_signature(secret, header, body) before you
trust the payload.
Keep your Twilio numbers
You do not have to port numbers on day one. A Twilio Elastic SIP trunk can point at the platform as a carrier path. Twilio keeps ownership of the numbers. ClutchCall terminates the SIP legs. The same external-agent routing applies. See SIP Trunking for trunk setup. See the Twilio migration guide for the full cutover sequence.Troubleshooting
Audio is garbled after the port
Audio is garbled after the port
The Media Streams payload was 8 kHz μ-law. The Call API speaks 8 kHz
pcm16. If your code still base64-decodes into μ-law bytes and sends
them without conversion, every sample is wrong. Convert with
telequick_agents.g711.ulaw_to_pcm / pcm_to_ulaw at the provider
boundary only.Barge-in stops the model but the caller still hears speech
Barge-in stops the model but the caller still hears speech
A truncate of the Realtime response only stops generation. Also call
await call.clear(). It drops the audio that is already queued for
playout. The sample’s "clear" event did this on Twilio.Webhook signature never verifies
Webhook signature never verifies
Check the signature against the exact raw request body. A
re-serialized JSON body (reordered keys, changed whitespace) computes a
different HMAC.
Related
From Twilio
The migration path: numbers, trunks, and rollout order.
LiveKit integration
The same external-agent seam for LiveKit Agents workers.
Run a LiveKit Agent on the Transport
The end-to-end external-agent recipe on the same seam.
SIP Trunking
Point an Elastic SIP trunk (or any carrier) at the platform.

