Skip to main content
The Stream message layer defines the application protocol shared by the WebSocket Stream API and the WebRTC API. It covers session setup, pipeline configuration, turn finalization, transcription and translation events, lifecycle events, errors, and usage modes. Transport mapping: Do not send audio in JSON messages. The init, configure, flush, and server event shapes are the same across transports.

Protocol Overview

Connection Lifecycle

Key Concepts

  • Init: Sent once per connection. Sets session-level parameters (sample rates, conversation ID).
  • Configure: Sent after init and again at any point to change languages, voice, or glossary. Each configure starts a new pipeline configuration. The server responds with configured when ready.
  • Flush: Signals the server to finalize the current input and produce all remaining output. The server responds with flushed when complete. Use this for manual turn-taking or to force processing of buffered audio.
  • Utterance: A continuous segment of speech. The server assigns an incrementing utterance_idx to each detected utterance. When VAD (Voice Activity Detection) is active, the server automatically splits speech into utterances.

Client Messages (Client -> Server)

Client control messages are JSON objects with a "type" field. Send them as WebSocket text frames on the WebSocket transport, or as stringified JSON messages on the WebRTC data channel. Audio transport is described in Input Audio.

init

Required as the first message on every connection. Sets connection-level parameters that remain fixed for the session lifetime.

configure

Initializes or reconfigures the translation pipeline. Must be sent after init. Can be sent again mid-session to change languages, voice, glossary, or features without reconnecting.
The server responds with a configured message when the pipeline is ready to accept audio.

Language Routes

Language routes define the translation directions the pipeline supports. Each route specifies a source and target language.
Single route — translates one direction only:
Bidirectional routes — the server detects which language is being spoken and translates to the other. Use this when both parties may speak either language (replaces the old can_lang_swap behavior):
When multiple routes are provided, the server performs language identification on the incoming audio and selects the matching route. You will receive a language_route event indicating which route was chosen for each utterance. Auto-detect mode (any language to one target) — use "*" as lang_in to accept any supported language and translate it to a fixed target. The server identifies the spoken language automatically and translates to the specified output language:
This translates any incoming language to English. The server will send a language_route event with the detected language filled in:
In auto-detect modes, the server may send tentative language_route messages (is_final: false) while language identification is still refining, followed by a single is_final: true message once the route is locked in. Clients that only care about the final answer can ignore tentative messages and act on is_final: true. You can combine auto-detect with an explicit route. For example, to translate English to Spanish and auto-detect everything else to English:
NOTE: When multiple language routes are specified, they will be matched in list order. The first route to match wins. In the following language route configuration, the server will always choose the first route because * matches any detected language. In this case, the consequence is: English speech will remain as English.
Wildcard catch-all route — add a "*" -> "*" route alongside your normal routes to handle speech in unexpected languages gracefully. When the detected language does not match any explicit route, the wildcard route is selected and the pipeline enters a transcription-only mode (no translation or TTS output). This is useful for prompting the user to switch languages:
When the wildcard catch-all route is triggered, you will receive a language_route event with the detected language echoed into both lang_in and lang_out. For the configuration above, if the user speaks French you will see:
Note that the server then emits transcription / transcription_ended for the utterance but no translation, translation_ended, output_audio, output_speech_started, or output_speech_ended (transcription-only mode). You can use this language_route to show a prompt to the user, for example: “It seems like you are speaking French. Would you like to switch your language?” If the user confirms, send a new configure with the appropriate routes.

Glossary Entries

Glossary entries either (a) keep a phrase verbatim across languages or (b) lock in a specific translation between two languages. Each entry is a map of language codes to the desired term in that language. Keep a phrase verbatim across languages — use "*". For brand names, product names, and other proper nouns that should never be translated, use the "*" wildcard:
This tells the pipeline that “Sanas” should remain “Sanas” in every language, and “ACME” should remain “ACME” in every language. Lock in a specific translation between 2+ languages — use language codes. To force a phrase in one language to a specific phrase in another, list each phrase by its language code:
Here, “servicio al cliente” spoken in Spanish becomes “service client” when translating to French, and “service client” spoken in French becomes “servicio al cliente” when translating to Spanish. A glossary entry only takes effect on a language route when both the input and output languages are specified. If either side isn’t covered, the entry is ignored for that route. Note that using a short code like es will match all variants of Spanish (es-419 and es-ES). You are also free to specify a long code (es-419) if you need more fine-grained tuning. Combining both. You can mix "*" with explicit language codes in the same entry. The "*" then acts as the term used whenever a route’s input or output language isn’t explicitly listed. For example, {"terms": {"*": "customer service", "es": "servicio al cliente", "fr": "service client"}} resolves Spanish → French to “service client” via the explicit codes, while Spanish → Russian falls back to “customer service” via the wildcard.

Features

The features array controls which pipeline mode is used. By default, you can leave the features array empty or omitted. Note that feature names are case-sensitive:

Input Audio

Streams audio data to the server after receiving configured. Audio transport depends on the API you are using: For WebSocket binary audio, use this format:
  • Encoding: 16-bit signed integer PCM (little-endian)
  • Channels: Mono (1 channel)
  • Sample rate: Must match input_sample_rate from init

flush

Signals the server to finalize processing of all buffered audio and produce remaining results. The server will respond with a flushed message after all output has been sent.

Server Messages (Server -> Client)

Server control and text messages are JSON objects with a "type" field. Receive them as WebSocket text frames on the WebSocket transport, or as stringified JSON messages on the WebRTC data channel. Audio transport is described in Output Audio. Note on optional fields: Fields documented as “or null” (such as request_id, probability, utterance_idx on errors) are omitted from the JSON when not present, rather than sent as "field": null. Your client should treat a missing key the same as a null value.

configured

Confirms the pipeline is ready to accept audio after a configure message.

input_speech_started

Indicates the server’s VAD has detected the start of speech in the input audio.

language_route

Indicates which language route was selected for the current utterance. Sent when the system identifies the spoken language, particularly useful when multiple language routes are configured.

transcription

Real-time transcription of the input audio. Complete words have been finalized and will not change. Partial words are in-progress and may be updated in subsequent messages.
Word object:

transcription_ended

Indicates that all transcription for a given utterance is complete. No further transcription messages will be sent for this utterance.

translation

Translated text derived from the transcription. Like transcription, includes complete (finalized) and partial (in-progress) words.

translation_ended

Indicates that all translation for a given utterance is complete.

input_speech_ended

Indicates the server’s VAD has detected the end of speech in the input audio. After this, the server will not accept further input audio for this utterance and will finalize the translation.

output_speech_started

Indicates the server has begun sending synthesized output audio for an utterance.

Output Audio

Synthesized audio from the translated text. Audio transport depends on the API you are using: For WebSocket binary audio, the format is:
  • Encoding: 16-bit signed integer PCM (little-endian)
  • Channels: Mono (1 channel)
  • Sample rate: Matches output_sample_rate specified in init

output_text_boundary

Timing information that maps a position in the output audio stream back to positions in the transcription and translation text. Use this to synchronize text highlighting with audio playback. Sent by default. Clients that don’t need word-level highlighting can opt out by setting the "hide_output_text_boundary" feature in configure, which suppresses these messages.
TextPosition object:

output_speech_ended

Indicates all synthesized audio for an utterance has been sent.

flushed

Confirms that a flush has been fully processed and all pending output has been sent.

error

Indicates an error occurred during processing.
Non-fatal errors (e.g., malformed JSON, unknown message type) are sent as error messages with code "STREAM_ERROR" but the connection remains open. You can continue sending messages after receiving an error. Errors originating from the backend translation pipeline will have their own error codes.

Examples

Simultaneous Mode (default)

In simultaneous mode, the server produces translation and audio output in real-time as the speaker talks, with minimal delay. Setup:
Flow:
  1. Send init with "realtime_playback": true, then configure
  2. Wait for configured
  3. Stream input audio over the selected transport continuously
  4. Receive streaming transcription, translation, and output audio in real time
  5. The server uses VAD to segment utterances automatically; each utterance gets its own utterance_idx
  6. Use flush when you want to force-finalize any pending output

Consecutive Mode with VAD (Automatic End-of-Speech)

In this mode, the speaker talks, the system detects when they stop, and then produces the full translation. This is the simplest approach for turn-based conversations. Setup:
Flow:
  1. Send init, then configure with "features": ["consecutive"]
  2. Wait for configured
  3. Stream input audio over the selected transport as the speaker talks
  4. The server’s VAD detects end-of-speech and sends input_speech_ended
  5. Receive transcription, translation, and output audio
  6. Receive output_speech_ended when all audio for the utterance has been sent
  7. For a multi-turn conversation, send a new configure to start the next turn (you can swap languages), or just keep streaming audio for the next utterance
Example message flow:

Consecutive Mode with Manual Flush

Use manual flush when you want explicit control over when the system processes the audio (e.g., push-to-talk with a button release, or after a fixed recording duration). Setup:
Flow:
  1. Send init, then configure with "features": ["consecutive"]
  2. Wait for configured
  3. Stream input audio over the selected transport while the user is speaking
  4. When the user finishes (e.g., releases push-to-talk button), send flush
  5. Receive transcription, translation, and audio output
  6. Receive flushed when all output is complete
  7. To start the next turn, simply resume sending audio (or send a new configure to change languages)
Example message flow:
Multiple turns on the same connection: After receiving flushed, you can immediately stream more audio and send another flush for the next turn. You do not need to re-send init or configure (unless you want to change settings). This avoids reconnection overhead.

Auto-Detect Mode (Any Language to One Target)

Use a wildcard "*" as lang_in to automatically detect the spoken language and translate it to a fixed target. This is ideal for scenarios like a help desk where callers may speak any language and all input should be translated to English: Setup:
Flow:
  1. Send init, then configure with a wildcard lang_in
  2. Wait for configured
  3. Stream input audio over the selected transport — the speaker can talk in any supported language
  4. The server identifies the language and sends a language_route event with the detected language. You may receive intermediate is_final: false messages while detection is still refining, followed by the final is_final: true message once the route is locked:
  5. Receive transcription (in the original language), translation (in English), and English output audio
  6. Display the detected language to the user so they know it was recognized correctly
You can also combine auto-detect with explicit routes:
In this example, English input is translated to Spanish. Any other language is translated to English.

Bidirectional Language Detection

For two-way conversations where either party may speak either language:
The server automatically detects which language is being spoken and routes to the appropriate translation direction. You will receive a language_route message for each utterance indicating which route was chosen.

Mid-Session Reconfiguration

You can change languages, voice, glossary, or features at any time by sending a new configure message. This is useful for:
  • Swapping speaker/language direction between turns
  • Changing the output voice
  • Updating the glossary
The server will tear down the current pipeline and build a new one. Wait for configured before sending new audio.

Supported Languages

Language codes use BCP-47 format (e.g., "en-US", "es-ES", "fr-FR"). To retrieve the current list of supported languages, use the Gateway languages API:

Error Handling

Connection Errors

Authentication is handled by the transport before the message layer starts. If authentication fails, the server returns an HTTP error before accepting the WebSocket connection:

Runtime Errors

During a session, errors are delivered as error messages over the active Stream transport. Non-fatal errors (bad JSON, unknown message type) do not close the connection — you can continue sending messages. Fatal errors (backend connection failure, gRPC stream errors) may be followed by the connection closing.

Inactivity Timeout

If no messages or media are sent or received for 60 seconds (configurable), the server closes the transport session.

Best Practices

  • Audio chunking: Send audio in small chunks (10-20 ms of audio per chunk) for the lowest latency. Larger chunks add latency equal to the chunk duration.
  • Real-time pacing: When streaming from a live microphone, audio is naturally paced. When streaming from a file, pace sends to match real-time (e.g., sleep 20 ms between 20 ms chunks) for optimal results. Set realtime_playback: true in init when playing audio back in real-time.
  • Handle partial results: Display partial transcriptions and translations for a responsive UX, but only persist complete words. Complete words will not change and are sent only once.
  • Buffer output audio: Accumulate a small buffer of output audio before starting playback to prevent stuttering. Most client-side audio playback libraries handle this automatically.
  • Use request_id: Include request_id in configure and flush messages to correlate responses, especially when sending multiple operations.
  • Reconnection logic: Implement exponential backoff for connection failures.
  • Graceful shutdown: Send flush before closing the connection to ensure all pending output is received.
  • Reuse sessions: For multi-turn conversations, reuse the same transport session and send new configure messages between turns rather than reconnecting.

Migration Guide: v2 Consecutive API to v3 Stream API

This guide helps you migrate from the v2 /v2/consecutive WebSocket endpoint to the v3 /v3/stream WebSocket endpoint. The message shape changes also apply to clients using the WebRTC data channel for Stream v3 messages.

Endpoint Change

Important: In v2, authentication failures happened after the WebSocket was established (close code 1008). In v3, authentication is validated before the upgrade, so failures result in a standard HTTP 401 response. Update your client error handling accordingly — you will receive an HTTP error, not a WebSocket close frame.

Protocol Changes

The biggest change is that v3 separates session initialization into two explicit steps (init + configure) instead of a single config message.

Initialization: config -> init + configure

v2: Single config message
v3: Two messages (init + configure)
Key differences:
  • Sample rates moved to init (sent once per connection)
  • lang_in/lang_out replaced by the language_routes array
  • can_lang_swap: true replaced by adding a second route with swapped languages
  • glossary changed from a flat string array to an array of {"terms": {...}} objects. Use "*" as the language key to apply a term across all languages (recommended default), and override specific languages as needed.
  • You must include "features": ["consecutive"] to get consecutive mode behavior
  • voice_id remains available on configure (same as v2 config)

Ready Signal: ready -> configured

v2:
v3:
The session_id field is no longer provided in the ready response. If you need a session identifier, use the conversation_id from init.

Audio Messages: JSON audio -> Binary WebSocket frames

v2 (input):
v3 (input): Raw PCM bytes sent as a WebSocket Binary frame (no JSON wrapper, no base64 encoding). v2 (output):
v3 (output): Raw PCM bytes received as a WebSocket Binary frame (no JSON wrapper, no base64 encoding). In v2, both input and output audio used JSON text frames with base64-encoded data. In v3, audio is sent and received as raw binary WebSocket frames, eliminating the ~33% base64 overhead. All other messages remain JSON text frames.

Transcription and Translation: added utterance_idx

v2 transcription and translation messages contained only complete and partial word arrays. v3 adds an utterance_idx field to each message so you can associate results with a specific utterance when multiple utterances are in flight. v2:
v3:

End-of-Speech: stop/flush / speech_stop -> flush / flushed + lifecycle events

v2 (client sends):
or:
Both stop and flush were accepted in v2 and had identical behavior. In v3, only flush exists, and it now accepts an optional request_id for correlation. v3 (client sends):
v2 (server confirms end-of-speech via VAD):
v3 (server lifecycle events):
In v3, the server provides more granular lifecycle events. The speech_stop message is replaced by input_speech_ended (when the server detects the speaker stopped) and output_speech_ended (when all output audio has been sent). The flushed message confirms a manual flush is complete. Note: in v2, a flushed message could arrive without a client-initiated stop/flush (triggered automatically by VAD via output_speech_ended). In v3, output_speech_ended and flushed are distinct events — flushed only arrives in response to a client flush.

Language Detection: languages -> language_route

v2:
v3:
The v3 version includes utterance_idx to associate the language detection with a specific utterance, and is_final to distinguish the locked-in route from tentative routes emitted while language identification is still refining.

can_lang_swap -> Multiple Language Routes

v2:
v3:
Instead of a boolean flag, v3 uses explicit bidirectional routes. You can also add a wildcard "*" -> "*" catch-all route to detect unexpected languages and prompt the user to switch (see Language Routes for details):

Glossary Format

v2:
Each string was treated as a term that should be preserved identically across all languages. v3:
The v3 format uses structured objects with per-language mappings. Always include a "*" wildcard key as the default so the term is handled correctly for any language. You can override specific languages alongside the wildcard. For brand names and proper nouns that should never change, a single "*" entry is all you need.

Speech Delimiter: speech_delimiter -> output_text_boundary

v2:
v3:
The utterance_idx moved to the top level, time was renamed to output_time, and the inner position objects no longer repeat utterance_idx. v3 emits boundaries by default (matching v2’s unconditional speech_delimiter), and clients that don’t need them can opt out by adding "hide_output_text_boundary" to the configure.features list to skip server-side alignment work.

Renamed Events

New Events in v3

The following server events are new in v3 and were not exposed on the v2 WebSocket:

Quick Migration Checklist

  1. Change WebSocket URL from /v2/consecutive to /v3/stream
  2. Replace the single config message with init + configure
  3. Add "features": ["consecutive"] to the configure message
  4. Replace JSON "type": "audio" input messages with raw PCM bytes in WebSocket Binary frames
  5. Handle output audio as WebSocket Binary frames containing raw PCM bytes (instead of JSON "type": "audio" text frames)
  6. Replace "type": "stop" (or "type": "flush") with "type": "flush" (now supports request_id)
  7. Handle "type": "configured" instead of "type": "ready"
  8. Handle "type": "language_route" instead of "type": "languages"
  9. Handle "type": "input_speech_ended" instead of "type": "speech_stop"
  10. Handle "type": "output_text_boundary" instead of "type": "speech_delimiter" (emitted by default like v2; add "hide_output_text_boundary" to configure.features to opt out)
  11. Update glossary format from flat strings to {"terms": {"*": "term"}} objects (always include the "*" wildcard key)
  12. Replace can_lang_swap: true with a second language route
  13. Handle utterance_idx on transcription and translation messages (new in v3)
  14. Handle "type": "translation_ended" (renamed from v2 "type": "translation_end")
  15. (Optional) Handle new lifecycle events for richer UX