Channels

Voice

Twilio Voice form webhook signed with X-Twilio-Signature. Post and Reply plan as TwiML <Say>. The HTTP layer writes that markup back as text/xml; it does not dispatch an outbound API call.

Point Twilio’s Voice webhook at a public HTTPS URL. Enable speech recognition so inbound includes SpeechResult. Use overlap: "drop" so a barge-in does not queue behind the current turn — see Overlap.

export TWILIO_ACCOUNT_SID='…'
export TWILIO_AUTH_TOKEN='…'
export VOICE_WEBHOOK_URL='https://…/voice'
export PORT=8080
cx.channels.add(
    "voice",
    via="self-host",
    account_sid=account_sid,
    auth_token=auth_token,
    webhook_url=webhook_url,
    bot_token="local",
)

@cx.on_message({"channel": "voice", "overlap": "drop"})
def on_speech(thread, msg, ctx):
    thread.post(msg.text)

# POST form -> cx.handle("voice", body, headers)
# respond with TwiML text/xml on that HTTP front
await cx.channels.add("voice", {
  via: "self-host",
  account_sid,
  auth_token,
  webhook_url,
  bot_token: "local",
})

cx.onMessage({ channel: "voice", overlap: "drop" }, async (thread, msg) => {
  await thread.post(msg.text)
})
// POST form → await cx.handle("voice", body, headers)
// respond with TwiML text/xml on that HTTP front

The URL in add() must be the public URL Twilio signed.