Stream dialogue in real-time
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 and Eleven v4 dialogue models only (model_id must start with eleven_v3 or eleven_v4).
Requirements
Section titled “Requirements”- An ElevenLabs account with an API key (authentication).
- The API key must have
Text to Speechpermissions. - Python or Node.js installed on your machine.
pip install python-dotenv websocketsnpm install dotenv ws
npm install @types/dotenv @types/ws --save-devCreate a .env file:
.env
ELEVENLABS_API_KEY=your_elevenlabs_api_key_herePick a voice ID from the Voice Library. The examples below use eleven_v4_turbo, which allows one registered voice per connection.
Open the WebSocket
Section titled “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).
import asyncio
import base64
import json
import os
from dotenv import load_dotenv
import websockets
load_dotenv()
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
VOICE_ID = "21m00Tcm4TlvDq8ikWAM"
MODEL_ID = "eleven_v4_turbo"
URI = (
"wss://api.elevenlabs.io/v1/text-to-dialogue/stream-input"
f"?model_id={MODEL_ID}&output_format=mp3_44100_128"
)import * as dotenv from "dotenv";
import * as fs from "node:fs";
import WebSocket from "ws";
dotenv.config();
const ELEVENLABS_API_KEY = process.env.ELEVENLABS_API_KEY;
const voiceId = "21m00Tcm4TlvDq8ikWAM";
const modelId = "eleven_v4_turbo";
const uri = `wss://api.elevenlabs.io/v1/text-to-dialogue/stream-input?model_id=${modelId}&output_format=mp3_44100_128`;
const websocket = new WebSocket(uri);
const outputDir = "./output";
try {
fs.accessSync(outputDir, fs.constants.R_OK | fs.constants.W_OK);
} catch {
fs.mkdirSync(outputDir);
}
const writeStream = fs.createWriteStream(`${outputDir}/dialogue-ws.mp3`, { flags: "a" });Register voices and stream text
Section titled “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).
async def stream_dialogue():
async with websockets.connect(URI) as websocket:
await websocket.send(
json.dumps(
{
"voices": [VOICE_ID],
"xi_api_key": ELEVENLABS_API_KEY,
}
)
)
line = (
"This is a longer line of dialogue used to exceed the minimum buffer so the model "
"starts generating streamed audio for the registered voice. "
)
await websocket.send(
json.dumps(
{
"inputs": [
{"text": line, "voice_id": VOICE_ID, "new_turn": False},
],
}
)
)
await websocket.send(json.dumps({"close_socket": True}))
os.makedirs("output", exist_ok=True)
out_path = "output/dialogue-ws.mp3"
with open(out_path, "wb") as audio_file:
while True:
raw = await websocket.recv()
msg = json.loads(raw)
if msg.get("error"):
raise RuntimeError(msg)
if msg.get("audio"):
audio_file.write(base64.b64decode(msg["audio"]))
if msg.get("is_final"):
break
print(f"Wrote {out_path}")
asyncio.run(stream_dialogue())websocket.on("open", () => {
websocket.send(
JSON.stringify({
voices: [voiceId],
xi_api_key: ELEVENLABS_API_KEY,
})
);
const line =
"This is a longer line of dialogue used to exceed the minimum buffer so the model starts generating streamed audio for the registered voice. ";
websocket.send(
JSON.stringify({
inputs: [{ text: line, voice_id: voiceId, new_turn: false }],
})
);
websocket.send(JSON.stringify({ close_socket: true }));
});
websocket.on("message", (data) => {
const msg = JSON.parse(data.toString());
if (msg.error) {
console.error(msg);
return;
}
if (msg.audio) {
writeToLocal(msg.audio, writeStream);
}
});
function writeToLocal(base64str: string, stream: fs.WriteStream) {
stream.write(Buffer.from(base64str, "base64"));
}
websocket.on("close", () => {
writeStream.end();
});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
Section titled “Run the script”python text-to-dialogue-websocket.pynpx tsx text-to-dialogue-websocket.tsYou should get an MP3 file under output/ (filename as in the example above).
Behaviour notes
Section titled “Behaviour notes”Buffering
Section titled “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
Section titled “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_v4_turbo, register exactly one voice in voices; eleven_v4 supports up to 10 registered voices.
Inactivity
Section titled “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
Section titled “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
Section titled “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
Section titled “Next steps”Choose the right WebSocket and compare message shapes.
Query parameters, message schemas, and examples.
When full request text is available without a WebSocket.
Single-voice non-v3 streaming over WebSockets.