# Exotel

> Phone calls over Exotel's Voicebot stream with ExotelFrameSerializer, used with FastAPIWebsocketTransport.

Source: https://polvallverdu.github.io/pipedog/docs/serializers/exotel/



## Overview [#overview]

**`ExotelFrameSerializer`** speaks the WebSocket protocol of Exotel's Stream and Voicebot applets.
Paired with [`FastAPIWebsocketTransport`](https://polvallverdu.github.io/pipedog/docs/transports/websocket.md), it lets a bot answer phone
calls:

* Caller audio (16-bit PCM at 8 kHz, base64 in `media` events) becomes `InputAudioRawFrame`s at
  the pipeline's input rate.
* Bot audio is resampled to 8 kHz and sent as `media` events.
* Touch-tone digits become `InputDTMFFrame`s.
* An `InterruptionFrame` sends `clear`.

There is no hang-up REST call: the call ends when the stream closes. See
[How phone calls connect](https://polvallverdu.github.io/pipedog/docs/telephony.md) for the overall flow.

<Cards>
  <Card title="Upstream page" href="https://docs.pipecat.ai/api-reference/server/services/serializers/exotel">
    Pipecat's documentation for the same serializer.
  </Card>

  <Card title="Source" href="https://github.com/polvallverdu/pipedog/tree/main/packages/serializer-exotel/src">
    `packages/serializer-exotel/src/exotel.ts`.
  </Card>

  <Card title="Exotel docs" href="https://support.exotel.com/support/solutions/articles/3000108630-working-with-the-stream-and-voicebot-applet">
    The Stream and Voicebot applets.
  </Card>
</Cards>

## Installation [#installation]

```bash
bun add @pipedog/serializer-exotel @pipedog/transport-websocket @pipedog/runner
```

```ts
import { ExotelFrameSerializer } from "@pipedog/serializer-exotel/exotel";
```

## Prerequisites [#prerequisites]

* An Exotel account with a call flow whose Voicebot applet points at your WebSocket URL.
* The stream SID (and call SID), which Exotel sends in its `start` message.

## ExotelFrameSerializer [#exotelframeserializer]

### Configuration [#configuration]

| Option | Type | Required | Description |
|---|---|---|---|
| `streamSid` | `string` | yes | The Exotel Media Stream SID. |
| `callSid` | `string \| null \| undefined` | no | The associated Exotel Call SID (optional, not used in this implementation). |
| `params` | `ExotelFrameSerializerInputParams \| null \| undefined` | no | Configuration parameters. |
| `name` | `string \| null \| undefined` | no | Custom name; defaults to `ClassName#count`. |
| `taskManager` | `BaseTaskManager \| null \| undefined` | no | Task manager for handling tasks. |

### InputParams [#inputparams]

`params` is an `ExotelFrameSerializer.InputParams` (`ExotelFrameSerializerInputParams`):

| Param                     | Type             | Default | Description                                                                                    |
| ------------------------- | ---------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `exotelSampleRate`        | `number`         | `8000`  | Sample rate Exotel uses, in Hz.                                                                |
| `sampleRate`              | `number \| null` | `null`  | Override for the pipeline input rate. `null` uses the pipeline's.                              |
| `resamplerClearAfterSecs` | `number \| null` | `0.2`   | Seconds of silence after which the stream resamplers clear their history. `null` never clears. |
| `ignoreRtviMessages`      | `boolean`        | `true`  | Drop RTVI messages instead of sending them to Exotel.                                          |

### Usage [#usage]

```ts title="exotel.ts"
import { parseTelephonyWebsocket } from "@pipedog/runner/utils";
import { ExotelFrameSerializer } from "@pipedog/serializer-exotel/exotel";
import {
  FastAPIWebsocketParams,
  FastAPIWebsocketTransport,
} from "@pipedog/transport-websocket/fastapi";
import { websocketFetchHandler } from "@pipedog/transport-websocket/fetch";
import type { WebsocketConnection } from "@pipedog/transport-websocket/socket";

async function runCall(connection: WebsocketConnection): Promise<void> {
  // Exotel sends "connected", then "start" with the stream and call SIDs.
  const [, callData] = await parseTelephonyWebsocket(connection);

  const serializer = new ExotelFrameSerializer({
    streamSid: callData.streamId ?? "",
    callSid: callData.callId,
  });

  const transport = new FastAPIWebsocketTransport(
    connection,
    new FastAPIWebsocketParams({
      audioInEnabled: true,
      audioOutEnabled: true,
      addWavHeader: false,
      serializer,
    }),
  );
  // ... build and run the pipeline with transport.input() and transport.output()
}

// The Voicebot applet's WebSocket URL points here.
const handler = websocketFetchHandler(runCall);
```

### Notes [#notes]

* `callSid` is stored but not used: Exotel has no hang-up call here.
* The audio is linear PCM, not μ-law: only resampled, never companded.
* An invalid DTMF digit is logged at info level and ignored.

## Runtime notes [#runtime-notes]

| Runtime            | Works | What it needs |
| ------------------ | ----- | ------------- |
| Bun                | ✅ yes | Nothing.      |
| Node.js            | ✅ yes | Nothing.      |
| Deno               | ✅ yes | Nothing.      |
| Cloudflare Workers | ✅ yes | Nothing.      |

Generated from `pipecat.runtimes` and `pipecat.compat` in `packages/serializer-exotel/package.json`: ✅ means the compat smoke test passes on that runtime.

Runs on Bun, Node, Deno and Cloudflare Workers. The package's golden tests replay vectors recorded
from upstream: at 8 kHz the wire output is byte-identical.
