> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fonoster.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bring your Twilio or Pipecat app

> Answer Fonoster calls with a bot built for Twilio Media Streams, such as a Pipecat bot, without changing its code.

If you already have a bot that speaks Twilio Media Streams, you can point Fonoster calls at it. Set
the stream's protocol to `twilio`, and Fonoster sends the same WebSocket messages Twilio sends. A
Pipecat bot that uses `TwilioFrameSerializer` runs as it is.

Use this when you already have the bot, or when your framework expects raw audio. To write new call
logic, the [Voice SDK](https://docs.fonoster.com/streams/voice-sdk.md) is simpler: Fonoster does the speech for you.

## What works

With the `twilio` protocol, Fonoster speaks the bidirectional Media Streams messages:

| Direction | Messages |
| - | - |
| Fonoster to your bot | `connected`, `start`, `media`, `dtmf`, `mark`, `stop` |
| Your bot to Fonoster | `media`, `mark`, `clear` |

* **Audio** is μ-law at 8000 Hz, mono, base64 in `media.payload`, both ways. Caller audio has
  `track: "inbound"`.
* **Barge-in** works the Twilio way. `clear` empties the queued audio, and every pending `mark` is
  echoed back.
* **Keypresses** arrive as `dtmf` with `dtmf.track: "inbound_track"` and `dtmf.digit`.
* **Hanging up**: close the WebSocket and Fonoster hangs up the call.

The ids in `start` map to Fonoster's:

| Field in `start` | Holds |
| - | - |
| `callSid` | The call's ref in Fonoster |
| `streamSid` | The session's ref |
| `accountSid` | Your workspace's access key ID (starts with `WO`) |
| `customParameters` | The call's metadata |

## What isn't supported

The `twilio` protocol carries audio, marks and keypresses only. Fonoster ignores any other message
and the call continues.

* **No TwiML.** Fonoster doesn't fetch or run TwiML. The stream's URL is the WebSocket itself.
* **No speech layer.** Fonoster doesn't transcribe the caller or speak text for your bot. Your bot
  brings its own speech-to-text and text-to-speech, as it does on Twilio. The stream's voice and
  speech settings don't apply.
* **No call control.** Your bot can't transfer the call, put it on hold or send keypresses. If you
  need those, use the `fonoster` protocol with the [Voice SDK](https://docs.fonoster.com/streams/voice-sdk.md).
* **Audio is μ-law 8 kHz only.** The audio format setting applies to `fonoster` streams.

## Point Fonoster at your bot

**1. Give your bot a public WebSocket address**

Fonoster dials your bot from the internet. Deploy the bot, or run it behind a tunnel, and note
the WebSocket URL it serves, such as `wss://bot.example.com/ws`. Fonoster won't dial
`localhost` or a private IP.

**2. Create a stream with the twilio protocol**

**Dashboard**

1. Go to **Streams** and choose **New stream**.
2. Name it, for example `Pipecat bot`.
3. Enter the bot's WebSocket address in **URL**.
4. Set **Protocol** to **Twilio**.
5. Choose **Create stream**.

Copy the **Signing secret (shown once)**. It isn't shown again.

**SDK**

Create the stream with `protocol: "twilio"`. The response holds its signing secret:

```javascript theme={"system"}
const apps = new SDK.Applications(client);
const { ref, signingSecret } = await apps.createApplication({
  name: "Pipecat bot",
  type: "STREAM",
  url: "wss://bot.example.com/ws",
  protocol: "twilio"
});
```

**CLI**

Create the stream with `--protocol twilio`:

```bash theme={"system"}
fonoster applications:create --name "Pipecat bot" \
  --url wss://bot.example.com/ws --protocol twilio
```

It prints the stream's ref and its signing secret.

Every connection to your bot is signed with this secret. Your bot runs without checking it,
but anyone who finds the URL can open a session. Add the check described in
[Verify request signatures](https://docs.fonoster.com/streams/verify-signatures.md).

**3. Point a number at the stream**

Go to **Numbers**, open the number, and under **When this number is called, send it to**
choose **Stream**, then your stream. See [Numbers and routing](https://docs.fonoster.com/telephony/numbers.md) for the
SDK and CLI.

**4. Call the number**

Your bot receives `connected` and `start`, then the caller's audio, just as it would from
Twilio.

## Run a Pipecat bot

A Pipecat bot built for Twilio needs no Fonoster-specific code. `parse_telephony_websocket`
recognizes Fonoster's `start` message as Twilio's, and `TwilioFrameSerializer` handles the audio,
marks and `clear`.

The one setting to check is how the bot hangs up. By default, `TwilioFrameSerializer` hangs up by
calling Twilio's REST API with your Twilio credentials, which can't end a Fonoster call. Turn that
off and let the bot close the WebSocket instead. Fonoster hangs up when the socket closes:

```python bot.py theme={"system"}
from fastapi import FastAPI, WebSocket
from pipecat.runner.utils import parse_telephony_websocket
from pipecat.serializers.twilio import TwilioFrameSerializer
from pipecat.transports.websocket.fastapi import (
    FastAPIWebsocketParams,
    FastAPIWebsocketTransport,
)

app = FastAPI()


@app.websocket("/ws")
async def ws(websocket: WebSocket):
    await websocket.accept()
    _, call_data = await parse_telephony_websocket(websocket)

    serializer = TwilioFrameSerializer(
        stream_sid=call_data["stream_id"],
        call_sid=call_data["call_id"],
        params=TwilioFrameSerializer.InputParams(auto_hang_up=False),
    )
    transport = FastAPIWebsocketTransport(
        websocket=websocket,
        params=FastAPIWebsocketParams(
            audio_in_enabled=True,
            audio_out_enabled=True,
            add_wav_header=False,
            serializer=serializer,
        ),
    )

    # Build and run your pipeline as usual:
    # Pipeline([transport.input(), stt, llm, tts, transport.output()])
```

When the pipeline ends, for example after an `EndTaskFrame`, Pipecat closes the WebSocket and the
call ends. Fonoster's tests run an unmodified Pipecat bot (pipecat-ai 1.12.0) this way: it hears the
caller, the caller hears it, `clear` stops playback on barge-in, keypresses reach it, and it hangs
up.

> **Hang up through the Twilio-compatible endpoint**
>
> Where the operator turns it on, Fonoster also serves Twilio's hang-up call:
> `POST {base}/2010-04-01/Accounts/{accountSid}/Calls/{callSid}.json` with `Status=completed` and
> HTTP Basic auth. `{accountSid}` is your workspace's access key ID. It answers `200`, or `404`
> with code `20404` when the call has already ended.
>
> Your install's configuration sets how Basic auth is checked:
>
> * **Workspace API keys**: the user is an API key ID (`AP…`) and the password its secret. The key
>   must belong to the workspace in the URL.
> * **One fixed pair** (on your own install): the user and the URL's `accountSid` are the pair's
>   `accountSid`, which must be your workspace's access key ID, and the password its `authToken`.
>
> Then point Pipecat's `auto_hang_up` at it with `base_url`. This example uses a fixed pair:
>
> ```python theme={"system"}
> import os
>
> serializer = TwilioFrameSerializer(
>     stream_sid=call_data["stream_id"],
>     call_sid=call_data["call_id"],
>     account_sid="WO7d2kq9x4m1c8b3n6p0r5t2v9y4z7a1e3",  # your workspace's access key ID
>     auth_token=os.environ["FONOSTER_HANGUP_TOKEN"],     # the pair's authToken
>     base_url="http://voice.example.com:8080",           # where the endpoint is served
>     params=TwilioFrameSerializer.InputParams(auto_hang_up=True),
> )
> ```
>
> See [Twilio compatibility: Hanging up](https://docs.fonoster.com/reference/twilio-compatibility.md#hanging-up).
>
> Closing the socket works on every install, so you don't need this to hang up.

## Next steps

> **[Verify request signatures](https://docs.fonoster.com/streams/verify-signatures.md)**
>
> Check that each connection comes from Fonoster.

> **[Twilio compatibility](https://docs.fonoster.com/reference/twilio-compatibility.md)**
>
> Every Media Streams message and field Fonoster sends and accepts.

> **[Build a stream with the Voice SDK](https://docs.fonoster.com/streams/voice-sdk.md)**
>
> Need transfers or text-in, text-out speech? Use the `fonoster` protocol.

> **[Numbers and routing](https://docs.fonoster.com/telephony/numbers.md)**
>
> Send a number's calls to your stream.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.