# How to Connect a Pipecat Bot to an Indian Phone Number (2026)

> Step-by-step: run a Pipecat voice bot with the Edesy frame serializer, expose it over wss, preflight it, and point an Indian number's inbound calls (or an outbound call) at it with one API request.

Source: https://edesy.in/blog/connect-pipecat-bot-to-indian-phone-number-2026

**To connect a Pipecat bot to an Indian phone number, buy a number on Edesy, run your bot with the
`EdesyFrameSerializer`, expose it over `wss://`, and call `PUT /v1/phone-numbers/:id/inbound` with
`target: "media_stream"` and your `stream_url`. Every call to that number then streams raw L16 audio to your bot,
and your bot's audio plays back to the caller.**

> **Get a number for your bot:** [Open the Edesy voice portal](https://voice-app.edesy.in/?utm_source=edesy.in&utm_medium=blog&utm_campaign=connect-pipecat-bot-to-indian-phone-number-2026). Complete KYC, buy a number and create an API key.

## What you need

| Item | Where it comes from |
|---|---|
| An Indian number | Edesy portal, after KYC (PAN and Aadhaar; about 24 hours) |
| An API key | Edesy portal, API keys |
| A Pipecat bot | The `pipecat-edesy` sample, or your own pipeline |
| STT, LLM and TTS keys | Your vendors (the sample uses Deepgram, OpenAI and Cartesia) |
| A public `wss://` URL | ngrok or similar for development; your load balancer in production |

## How the pieces fit

```
caller --phone network--> Edesy --wss:// (L16 PCM)--> your Pipecat bot
                            ^                               |
                            +---------- audio back ---------+
```

Edesy handles the phone call. Your bot handles listening, thinking and speaking. You never configure a SIP stack.

## Step 1: Run the sample bot

The sample has three files that matter:

- `edesy_serializer.py`: the `EdesyFrameSerializer`, which speaks Edesy's WebSocket protocol. Copy it into your
  project.
- `bot.py`: the Pipecat pipeline (Deepgram STT, OpenAI LLM, Cartesia TTS, Silero VAD). Swap any vendor.
- `server.py`: a FastAPI `/ws` endpoint that accepts the call, reads the metadata frame and runs the bot.

```bash
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env    # add your Deepgram, OpenAI and Cartesia keys
python server.py        # listens on :7860, WebSocket path /ws
```

## Step 2: Expose it over TLS

Edesy connects to `wss://` URLs. In development, `ngrok http 7860` gives you a `wss://...ngrok.app/ws` address. In
production, terminate TLS at your load balancer. Accept the WebSocket upgrade quickly and do any auth at the
upgrade, not after it.

## Step 3: Preflight the URL

Before you place a call, check that Edesy can reach the bot:

```bash
curl -X POST https://voice-api.edesy.in/v1/stream-test \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"stream_url":"wss://your-host/ws","sample_rate":16000,"seconds":2}'
```

The response is a checklist by stage (DNS, TCP, TLS, upgrade, audio), so a broken tunnel or a non-WebSocket
route shows up by name.

## Step 4: Point the number at the bot

```bash
curl -X PUT https://voice-api.edesy.in/v1/phone-numbers/<PHONE_NUMBER_ID>/inbound \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"target":"media_stream","stream_url":"wss://your-host/ws","sample_rate":16000}'
```

Get `<PHONE_NUMBER_ID>` from `GET /v1/phone-numbers`. `sample_rate` is `16000` or `8000` and must match your
pipeline; the sample reads it from the call metadata, so it stays in sync. Call the number and your bot answers.
To detach, send `DELETE` to the same path.

You can do the same thing in the portal: open the number, choose "Media stream" under "When a call comes in", and
paste the URL.

## Step 5: Make outbound calls

The same bot works for outbound. Pass `stream_url` when you create the call:

```bash
curl -X POST https://voice-api.edesy.in/v1/calls \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"from":"+9180XXXXXXXX","to":"+9198XXXXXXXX",
       "stream_url":"wss://your-host/ws","stream_sample_rate":16000,
       "custom_parameters":{"assistant_id":"asst_456"}}'
```

`custom_parameters` arrive in the metadata frame, so the bot knows which assistant, campaign or customer the
call is for without a lookup.

## The wire protocol in short

| Direction | Frame | Content |
|---|---|---|
| Edesy to bot | Text, once on connect | JSON metadata: `sampleRate`, `callSid`, `from`, `to`, `customerData` |
| Edesy to bot | Binary | Caller audio, L16 PCM mono |
| Bot to Edesy | Binary | Audio to play to the caller |
| Bot to Edesy | Text | `{"type":"killAudio"}` to stop playback (barge-in), `{"type":"disconnect"}` to hang up |
| Edesy to bot | Text | `{"event":"dtmf","dtmf":"5"}` for a keypress |

For more on frame sizes, barge-in and debugging, see [WebSocket audio streaming for voice bots](https://edesy.in/blog/websocket-audio-streaming-voice-bot-2026).

## Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| Call connects, bot never gets a connection | URL not reachable from the internet | Run `/v1/stream-test`; check the tunnel |
| Bot crashes at the start of the call | Metadata frame not handled | Read the first text frame before audio |
| Robotic or chipmunk audio | Sample rate mismatch | Use the same rate on the number and in the pipeline |
| Bot talks over the caller | Barge-in not wired | Send `killAudio` when VAD hears the caller |
| Outbound call never reaches the bot | Request had no `stream_url` or `application_id` | Add one |

Stream events (`call.stream.connected`, `failed`, `started`, `stopped`) also appear on the call's timeline at
`GET /v1/calls/:sid/events`.

## Costs

Edesy charges Rs 0.50 per minute for the call (60-second pulse, unanswered calls free) plus number rental:
Rs 499 per month for mobile or 92-series, Rs 449 for landline. Prices exclude 18% GST. STT, LLM and TTS are billed
by your vendors. See [Voice API pricing](https://edesy.in/voice-api/pricing).

## Media Streams, Conversation Relay or SIP?

If you'd rather not run STT and TTS yourself, [Conversation Relay](https://edesy.in/voice-api/conversation-relay) sends your bot
text instead of audio. If your agent runs on LiveKit Cloud, Vapi, Retell or Bolna, a SIP route on
[SIP trunking](https://edesy.in/sip-trunking) may be simpler. All three are options on [call routing](https://edesy.in/call-routing). The
[Media Streams page](https://edesy.in/voice-api/media-streams) and the [Voice API docs](https://edesy.in/docs/voice-api) have the full reference.

## Frequently Asked Questions

### Can I use Pipecat with an Indian phone number?

Yes. Buy an Indian number on Edesy after KYC, run your Pipecat bot with the EdesyFrameSerializer, and point the number's inbound calls at the bot's WebSocket URL with PUT /v1/phone-numbers/:id/inbound. Outbound calls use POST /v1/calls with a stream_url.

### What audio format does Edesy send to Pipecat?

Raw L16 PCM (signed 16-bit little-endian), mono, at 8 kHz or 16 kHz, in binary WebSocket frames. There is no base64 or mu-law, so the serializer mostly moves bytes.

### Which STT, LLM and TTS can I use?

Any that Pipecat supports. The sample uses Deepgram for STT, OpenAI (or Gemini) for the LLM, Cartesia for TTS and Silero for voice activity detection. You pay those vendors directly.

### How do I know my bot is reachable before I make a call?

Use POST /v1/stream-test with your stream_url and sample_rate. It connects from Edesy's side and reports each stage (DNS, TCP, TLS, upgrade, audio), so a broken tunnel or wrong path is named instead of guessed.

### What does it cost?

Edesy charges Rs 0.50 per minute for the call on a 60-second pulse, unanswered calls free, plus number rental (Rs 499 per month for a mobile or 92-series number). Prices exclude 18% GST. Your AI vendor costs are separate.

### Should I use Media Streams or SIP with Pipecat?

Media Streams is the simplest: one WebSocket, no SIP stack. If your setup already runs on SIP (for example a LiveKit Cloud agent), point the number at a SIP URI instead. Both are inbound targets on the same number.
