Stream dialogue in real-time

This guide shows you how to stream Eleven v3 dialogue audio over the Text to Dialogue WebSocket.

The Text to Dialogue WebSocket (/v1/text-to-dialogue/stream-input) keeps a single connection open while you send dialogue lines and receive base64-encoded audio chunks. It is intended for Eleven v3 dialogue models only (model_id must start with eleven_v3).

This guide covers the Text to Dialogue WebSocket. For Flash, Multilingual v2, or other non-v3 TTS models, use the Realtime TTS WebSocket. For a side-by-side summary of both protocols, see Text to Speech vs Text to Dialogue WebSockets.

Requirements

  • An ElevenLabs account with an API key (authentication).
  • The API key must have Text to Speech permissions.
  • Python or Node.js installed on your machine.

Setup

1pip install python-dotenv websockets

Create a .env file:

.env
$ELEVENLABS_API_KEY=your_elevenlabs_api_key_here

Pick a voice ID from the Voice Library. The examples below use eleven_v3_conversational, which allows one registered voice per connection.

Open the WebSocket

Connect to wss://api.elevenlabs.io/v1/text-to-dialogue/stream-input with query parameters such as model_id and output_format. You can send the API key in the xi-api-key header or in the first JSON message (shown here in the body for a single pattern across languages).

1import asyncio
2import base64
3import json
4import os
5
6from dotenv import load_dotenv
7import websockets
8
9load_dotenv()
10ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
11VOICE_ID = "21m00Tcm4TlvDq8ikWAM"
12MODEL_ID = "eleven_v3_conversational"
13
14URI = (
15 "wss://api.elevenlabs.io/v1/text-to-dialogue/stream-input"
16 f"?model_id={MODEL_ID}&output_format=mp3_44100_128"
17)

Register voices and stream text

Send a first message that includes voices (required) and xi_api_key if you did not set the xi-api-key header. Then send one or more frames with inputs: each item has text, voice_id, and optional new_turn.

The server buffers text until it has enough context (about 40 characters and 8 words), then emits audio chunks. Response fields use snake_case (for example is_final).

1async def stream_dialogue():
2 async with websockets.connect(URI) as websocket:
3 await websocket.send(
4 json.dumps(
5 {
6 "voices": [VOICE_ID],
7 "xi_api_key": ELEVENLABS_API_KEY,
8 }
9 )
10 )
11
12 line = (
13 "This is a longer line of dialogue used to exceed the minimum buffer so the model "
14 "starts generating streamed audio for the registered voice. "
15 )
16 await websocket.send(
17 json.dumps(
18 {
19 "inputs": [
20 {"text": line, "voice_id": VOICE_ID, "new_turn": False},
21 ],
22 }
23 )
24 )
25
26 await websocket.send(json.dumps({"close_socket": True}))
27
28 os.makedirs("output", exist_ok=True)
29 out_path = "output/dialogue-ws.mp3"
30 with open(out_path, "wb") as audio_file:
31 while True:
32 raw = await websocket.recv()
33 msg = json.loads(raw)
34 if msg.get("error"):
35 raise RuntimeError(msg)
36 if msg.get("audio"):
37 audio_file.write(base64.b64decode(msg["audio"]))
38 if msg.get("is_final"):
39 break
40 print(f"Wrote {out_path}")
41
42
43asyncio.run(stream_dialogue())

close_socket flushes any buffered text, sends remaining audio, then a final frame with is_final: true before the connection closes. To keep the connection open between lines, omit close_socket until the session ends; use flush to force audio for shorter buffers without closing.

Run the script

1python text-to-dialogue-websocket.py

You should get an MP3 file under output/ (filename as in the example above).

Behaviour notes

Buffering

Unlike the TTS WebSocket chunk_length_schedule, dialogue streaming uses a fixed server threshold (character and word count) before the first partial audio. If you send short lines and hear delays, batch slightly more text per inputs frame or send flush: true to force generation without closing the socket.

Turns and voices

Set new_turn: true when a speaker finishes a turn so prosody resets cleanly. Changing voice_id between inputs entries also starts a new turn. With eleven_v3_conversational, register exactly one voice in voices; eleven_v3 supports up to 10 registered voices.

Inactivity

If the server receives no client message for 20 seconds, the connection ends. Send {"keep_alive": true} to reset the timer without synthesizing audio.

Concurrency

Each open connection holds one dialogue session for as long as it stays open, drawn from a dedicated pool separate from your plan’s standard concurrency limit. Audio generated over the connection does not count toward standard concurrency. See Text to Dialogue concurrency.

Alignment

Add sync_alignment=true to the query string to receive alignment objects (snake_case timing arrays) on chunks when available. See the API reference.

Next steps