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 API is rolling out to customers gradually over the coming weeks. You may not have access immediately.

This feature is currently offered as a Beta Service. By enabling access, you agree to the Beta Services Addendum and acknowledge that usage will be billed at $70/1M chars starting on your next billing date, unless otherwise agreed with your account owner.

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.
  • Workspace level access to the Text to Dialogue WebSocket (product feature)
  • 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.

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