Skip to main content

WebSocket protocol

When a call hits Stream or Voicebot, Exotel opens a WebSocket to your endpoint and streams raw PCM audio every ~100 ms. Bidirectional (Voicebot) sessions can send audio back on the same socket.

Event support by applet

EventStream (unidirectional)Voicebot (bidirectional)
connected
start
media (receive)
dtmf (receive)
mark (receive)
stop
media (send)
mark (send)
clear (send)

Events — Exotel → your server

connected

{ "event": "connected" }

start

{
"event": "start",
"sequence_number": "1",
"stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx",
"start": {
"stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx",
"call_sid": "CAxxxxxxxxxxxxxxxxxxxxxxxx",
"account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxx",
"from": "+919876543210",
"to": "+918047491899",
"custom_parameters": { "key1": "value1" },
"media_format": {
"encoding": "audio/x-raw",
"sample_rate": "8000",
"bit_rate": "16"
}
}
}

media

{
"event": "media",
"sequence_number": "3",
"stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx",
"media": {
"chunk": "2",
"timestamp": "200",
"payload": "<base64-encoded PCM>"
}
}

dtmf (Voicebot only)

{
"event": "dtmf",
"sequence_number": "7",
"stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx",
"dtmf": { "digit": "5", "duration": "100" }
}

mark (Voicebot only)

Sent when audio you previously sent has finished playing.

{
"event": "mark",
"sequence_number": "15",
"stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx",
"mark": { "name": "my-label" }
}

stop

{
"event": "stop",
"sequence_number": "20",
"stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx",
"stop": {
"call_sid": "CAxxxxxxxxxxxxxxxxxxxxxxxx",
"account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxx",
"reason": "callended"
}
}

reason: stopped (applet ended) · callended (caller hung up)

Events — your server → Exotel (Voicebot only)

media — send audio to caller

{
"event": "media",
"stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx",
"media": { "payload": "<base64-encoded PCM>" }
}

mark — tag a playback position

{
"event": "mark",
"stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx",
"mark": { "name": "turn-3-end" }
}

clear — flush buffered audio (barge-in)

{ "event": "clear", "stream_sid": "MZxxxxxxxxxxxxxxxxxxxxxxxx" }

Audio format

PropertyValue
CodecRaw PCM (linear16) — uncompressed
Bit depth16-bit signed, little-endian
ChannelsMono
Default sample rate8 000 Hz
Supported rates8 000 · 16 000 · 24 000 Hz
TransportBase64
Chunk size3 200–100 000 bytes, multiple of 320
Max session duration60 minutes

Echo server

import asyncio, json, websockets

async def handle(ws):
stream_sid = None
async for msg in ws:
ev = json.loads(msg)
match ev["event"]:
case "start":
stream_sid = ev["start"]["stream_sid"]
case "media":
await ws.send(json.dumps({
"event": "media",
"stream_sid": stream_sid,
"media": {"payload": ev["media"]["payload"]}
}))
case "stop":
break

async def main():
async with websockets.serve(handle, "0.0.0.0", 5001):
await asyncio.Future()

asyncio.run(main())