> 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.

# Vonage

Connect a Vonage call to a WebSocket relay and forward its 16-bit PCM audio to Noesis.

The Vonage Voice API connects a call to a WebSocket and sends its audio as binary frames of 16-bit PCM. Run a relay that forwards those frames to Noesis.

## 1. Connect the call to your relay

In your NCCO, connect the call to the relay's WebSocket. Values in `headers` arrive in the first message, so use them to pass your call ID:

```json
[
  {
    "action": "connect",
    "endpoint": [
      {
        "type": "websocket",
        "uri": "wss://relay.example.com/vonage",
        "content-type": "audio/l16;rate=16000",
        "headers": { "call_id": "your-call-id" }
      }
    ]
  }
]
```

The WebSocket becomes part of the call: Vonage sends it the call's audio and plays back anything it sends. The relay sends nothing back. To keep the call connected to a person or bot at the same time, add the WebSocket as another leg of the same conversation.

## 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
# vonage_relay.py: Vonage WebSocket -> your server -> Noesis
import asyncio, 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(vonage):
    call = None
    try:
        async for message in vonage:
            if call is None:                        # the first message is JSON with your headers
                info = json.loads(message) if isinstance(message, str) else {}
                call = NoesisCall(on_answer, encoding="pcm_s16le", sample_rate=16000,
                                  call_id=info.get("call_id"))
                await call.open()
            if isinstance(message, bytes):
                await call.send(message)            # 16-bit PCM, 20 ms per frame
    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())
```

For `audio/l16;rate=8000`, use `sample_rate=8000`. To not record calls, create each call with `record="false"` as well.
