# Media streams

> Answer a contact-centre platform's calls over a WebSocket: POST /v1/streams, the five dialects, the URL, metadata as vars.

A media stream connection lets a partner's contact-centre platform (SparkTG,
Twilio, Plivo, Exotel or Vonage) keep its own numbers and stream each call's
audio to one of your voice agents over **one WebSocket per call**. The partner
keeps the number, IVR, queue and CRM; Talqing runs the agent. Create one with
`POST /v1/streams` and hand the partner the `url` it returns.

Use it when the other side will not move a number onto a [SIP
trunk](https://docs.talqing.com/telephony/overview.md). Compared with a Talqing phone number, a stream
agent **cannot transfer to a human**, and on every platform but SparkTG it
cannot hang up.

## Create a connection

The agent must be a published `voice` agent. In the dashboard: **Telephony →
Media Streams → New connection**.

```bash cURL
curl -X POST https://api.talqing.com/v1/streams \
  -H "Authorization: Bearer $TALQING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "SparkTG - Acme support line",
    "dialect": "sparktg",
    "agent_id": "81399608-4930-4ff1-9281-f1f4932d1999",
    "region": "in"
  }'
```

```typescript TypeScript
const connection = await talqing.telephony.streams.create({
  createStreamConnectionRequest: {
    name: "SparkTG - Acme support line",
    dialect: "sparktg",
    agent_id: "81399608-4930-4ff1-9281-f1f4932d1999",
    region: "in",
  },
});
console.log(connection.url);
```

```python Python
connection = talqing.telephony.streams.create(
    name="SparkTG - Acme support line",
    dialect="sparktg",
    agent_id="81399608-4930-4ff1-9281-f1f4932d1999",
    region="in",
)
print(connection["url"])
```

The response (`201`) carries the full `url` on this and every later read. The
dashboard's setup panel has the partner-side steps and, for Twilio, Plivo and
Vonage, a snippet to paste.

## Platforms

`dialect` decides what the agent can do on the call.

| `dialect` | Partner connects with | Agent can hang up | Agent can press keys | Caller's number on the stream |
| --- | --- | --- | --- | --- |
| `sparktg` | A URL configured per tenant at onboarding | Yes | Yes | Yes |
| `twilio` | `<Connect><Stream>` in TwiML | No | No | **No** |
| `plivo` | `<Stream bidirectional="true">` in XML | No | Yes | No |
| `exotel` | A Voicebot applet in the call flow | No | No | Yes |
| `vonage` | A `connect` action in an NCCO | No | No | No |

> **Warning:**
> On every platform but SparkTG, **the agent finishing does not hang up.** The
> stream closes and the caller returns to the partner's flow. On Twilio and
> Vonage, if nothing follows the stream, the caller hears silence. Put the next
> step there: the TwiML verb after `</Connect>`, the next applet, or the next NCCO
> action. That is also where escalation to a person belongs.

## The URL

```
wss://streams.{region}.talqing.com/v1/streams/connect/{dialect}/{workspace_id}/{agent_id}
```

- `region` is `us` (default) or `in`. Pick the one nearest the partner's media.
  **Changing `region` changes the URL**; the old host stops answering for the
  connection, so the partner must switch. URLs on `streams.talqing.com` are `us`.
- Everything is in the path because Twilio's `<Stream url>` allows no query string.
- **One connection per agent per dialect.** Two partners on the same platform
  would share one URL, so give each its own agent.

> **Warning:**
> **The URL is not a credential and cannot be rotated.** It is derived from the
> dialect, workspace and agent, so deleting and recreating the connection returns
> the same URL. Anyone holding it can place calls against your credit balance and
> question the agent. To stop it answering, set `status: "disabled"` with
> `PATCH /v1/streams/{connection_id}` or point the connection at another agent.
> Disabling refuses new calls at once; calls in progress continue.

## Per-call metadata becomes `vars`

The platform's per-call bag (Twilio `<Parameter>`, SparkTG SIP-header attributes,
Plivo `extraHeaders`, Exotel query parameters, Vonage NCCO `headers`) is passed
through verbatim: a key `svcId` reaches the agent as `{{vars.svcId}}`. A key that
is not a valid variable name (a hyphen, a leading digit) is skipped and noted on
the call's timeline. See [variables](https://docs.talqing.com/agents/variables.md).

Three keys also set call facts:

| Key | Effect |
| --- | --- |
| `from` | The calling party's number. |
| `to` | The called party's number. |
| `direction` | `inbound` or `outbound` (Twilio's `outbound-api` / `outbound-dial` accepted). Fills `{{system_vars.direction}}`; absent when not sent. |

The person is `from`, or `to` when `direction` is `outbound`. Their number fills
`{{system_vars.human_phone_number}}` and groups the call into their
[conversation](https://docs.talqing.com/observability/conversations.md); the other fills
`{{system_vars.agent_phone_number}}`. A value that is not a phone number
(`anonymous`, an unfilled placeholder) is ignored and the caller is treated as
unidentified. Plivo and Vonage may send digits without `+`.

> **Note:**
> Twilio, Plivo and Vonage put no numbers on the stream. Without `from` in the
> metadata every call is anonymous: no caller number for the agent, no history
> across calls. The dashboard's Twilio snippet passes `{{From}}`, `{{To}}` and
> `{{Direction}}` from a TwiML Bin; a partner serving TwiML from its own webhook
> must substitute them itself.

## How a stream call differs from a phone call

A stream call is a `voice` call with `type` `STREAM` in [Calls](https://docs.talqing.com/observability/calls.md):
transcribed, [analysed](https://docs.talqing.com/observability/call-analysis.md) and
[recorded](https://docs.talqing.com/observability/recordings.md) per the agent's settings, billed at the
voice platform fee plus your provider spend, capped at three hours. Differences:

- **No transfer.** A [`transfer`](https://docs.talqing.com/tools/transfer.md) operation fails on a stream;
  route escalation through the partner's flow.
- **Background audio is off** (ambient and thinking sounds). The agent's voice is
  unaffected.
- **Keypad is out of band only**; tones inside the audio are not detected. See
  [keypad input](https://docs.talqing.com/agents/keypad.md).
- **A Talqing release ends calls in progress** with close reason "The call was
  ended by a Talqing deployment". Those calls carry no platform fee.
- **No per-connection concurrency cap.** Only an empty credit balance refuses calls.

## Audio formats

The platform announces the format per call; nothing to configure.

| Format | Platforms |
| --- | --- |
| G.711 μ-law, 8 kHz | Twilio (always), Plivo |
| 16-bit linear PCM, 8/16/24 kHz | SparkTG, Plivo, Exotel, Vonage |
| Opus | Refused, with an error naming it. |

Unidirectional (listen-only) streams are not supported.

## Connection reference

| Field | Values |
| --- | --- |
| `name` | 1–120 characters. |
| `dialect` | `sparktg`, `twilio`, `plivo`, `exotel`, `vonage`. |
| `agent_id` | The voice agent that answers. Changeable with `PATCH`. |
| `region` | `us` (default) or `in`. Changeable with `PATCH`; changes `url`. |
| `status` | `active` or `disabled`. |
| `readiness` | `live`, `needs_agent` (agent never published), `disabled`. |
| `url` | The WebSocket URL to give the partner. |

`GET /v1/streams` lists connections; `DELETE /v1/streams/{connection_id}` removes
one and keeps the calls it carried.

## Debugging

| Symptom | Cause |
| --- | --- |
| Calls never connect, nothing in Calls | The partner abandoned the call before the WebSocket handshake finished (SparkTG allows 5 seconds). Check the URL is reachable from outside your network. |
| Handshake refused with `403` | The URL names no connection (check dialect and agent segments), or the connection is disabled. |
| Call connects and ends at once | The agent was never published, or the workspace has no credit. The call shows as failed with the reason. |
| Agent speaks, then silence | The stream closed and nothing follows it in the partner's flow. |
| Static | Sample-rate or byte-order mismatch on the partner's side. The call detail shows the negotiated format. |
| Agent missed words | Audio arrived late after a network stall and was dropped. The call's timeline says how much. |
| `{{vars.x}}` is empty | The key was missing or not a valid variable name; the timeline lists both. |

## Next

- [Keypad input](https://docs.talqing.com/agents/keypad.md): Digits the caller types and digits the agent presses.
- [Telephony overview](https://docs.talqing.com/telephony/overview.md): Answering calls on your own carrier numbers instead.
