> For AI agents: the documentation index is at https://platform.detectif.ai/llms.txt. Every page is also available as Markdown by adding `.md` to its path.

# Exotel

Stream an Exotel call to a relay with the Stream or Voicebot applet, and forward the callee's 16-bit PCM audio to Noesis.

Exotel's Stream and Voicebot applets send a call's audio to a WebSocket as JSON messages, with 16-bit PCM audio at 8 kHz in base64. Run a relay that decodes the audio and forwards it to Noesis.

## 1. Stream the call to your relay

In your call flow, add a Stream applet (or a Voicebot applet) and set its URL to your relay, for example `wss://relay.example.com/exotel`. Exotel calls the relay when the applet runs.

## 2. Forward the audio to Noesis

The relay uses `noesis_relay.py` from the [relay helper](https://platform.detectif.ai/docs/noesis/websocket-client/#relay-helper):

```python
# exotel_relay.py: Exotel stream -> your server -> Noesis
import asyncio, base64, json

import websockets

from noesis_relay import NoesisCall

async def on_answer(event):
    if event["type"] == "classification":
        print(event["call_id"], event["label"])     # your own logic

async def handle(exotel):
    call = None
    try:
        async for raw in exotel:
            msg = json.loads(raw)
            if msg.get("event") == "start":
                call = NoesisCall(on_answer, encoding="pcm_s16le", sample_rate=8000,
                                  call_id=msg["start"].get("call_sid"))
                await call.open()
            elif msg.get("event") == "media" and call is not None:
                await call.send(base64.b64decode(msg["media"]["payload"]))   # 16-bit PCM
            elif msg.get("event") == "stop":
                break
    except websockets.ConnectionClosed:
        pass
    if call is not None:
        await call.close()

async def main():
    async with websockets.serve(handle, "0.0.0.0", 8080):
        await asyncio.Future()                      # run until stopped

asyncio.run(main())
```

If your stream is set to 16 kHz, use `sample_rate=16000`. To not record calls, create each call with `record="false"` as well.
