> ## Documentation Index
> Fetch the complete documentation index at: https://developer.suki.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket Streaming Wire Format

> Reference for Ambient /ws/stream and Dictation /ws/transcribe: message format, order, example flow, and audio format and chunking

Use this reference for outbound WebSocket messages and audio encoding. Choose the tab for your product. For when to use ambient versus Dictation, see [Ambient vs Dictation streaming](/documentation/how-to/audio-streaming/audio-stream).

Both endpoints send **JSON text frames** only (one JSON object per send). Do not send raw binary audio frames.

<Tabs>
  <Tab title="Ambient /ws/stream">
    ## Message format

    Each outbound message is a JSON object with a **`type`** field. For messages that carry a payload, use the **`data`** field.

    The **`data`** value must be:

    * Standard Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648))
    * An encoding of the raw bytes you intend to send
    * Sent as a JSON string, regardless of the programming language you use

    <Warning>
      Do not use:

      * Hex encoding
      * URL-safe Base64
      * Raw binary inside JSON strings
    </Warning>

    ### Start the stream segment

    Send one **`START_TIME`** message before the audio chunks for a stream segment:

    ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    { "type": "START_TIME", "data": "<base64>" }
    ```

    * **`data`**: Base64 of UTF-8 bytes of an RFC 3339 timestamp
    * **Example timestamp**: **`2026-04-25T12:34:56Z`**

    ### Send audio chunks

    Send audio with **`type`** set to **`AUDIO`**:

    ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    { "type": "AUDIO", "data": "<base64>" }
    ```

    * **`data`**: Base64 of raw PCM audio bytes

    ### Send control events

    Send control events with **`type`** set to **`EVENT`**:

    ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    { "type": "EVENT", "event": "PAUSE" }
    ```

    Use the **`event`** field, not **`data`**. Supported values are **`PAUSE`**, **`RESUME`**, **`KEEP_ALIVE`**, **`CANCEL`**, and **`ABORT`**.

    <Note>
      The ambient stream handler acts on **`PAUSE`**, **`RESUME`**, and **`CANCEL`**. While the stream is **paused**, send **`KEEP_ALIVE`** at least once every **five seconds** per the **idle-timeout guidance** (maximum pause **30 minutes**).
    </Note>

    **Common use cases**:

    * Pause or resume audio.
    * Keep the connection alive.
    * Cancel or abort a stream.

    `EVENT` messages can appear at any point in the stream where control is needed.

    ### End the stream segment

    Send the final **`AUDIO`** message with **`data`** set to **`RU9G`**:

    ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    { "type": "AUDIO", "data": "RU9G" }
    ```

    <Note>
      **`RU9G`** is Base64 for ASCII **`EOF`** (**`0x45 0x4F 0x46`**).
    </Note>

    <Warning>
      Do not:

      * Send **`EOF`** as plain text.
      * Use custom types like **`end_of_stream`**.
      * Use binary EOF signaling.
    </Warning>

    ## Required message order

    For each stream segment:

    1. Send one **`START_TIME`** message.
    2. Send one or more **`AUDIO`** messages with Base64 PCM audio.
    3. Send any **`EVENT`** messages when control is needed.
    4. Send a final **`AUDIO`** message with **`data`** set to **`RU9G`**.

    ## Example flow

    This example shows the outbound message sequence for one ambient stream segment: one **`START_TIME`** message, multiple **`AUDIO`** chunks, an optional **`EVENT`**, and the final **`RU9G`** end marker.

    ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    { "type": "START_TIME", "data": "<base64(timestamp)>" }

    { "type": "AUDIO", "data": "<base64(pcm chunk 1)>" }
    { "type": "AUDIO", "data": "<base64(pcm chunk 2)>" }

    { "type": "EVENT", "event": "PAUSE" }

    { "type": "AUDIO", "data": "<base64(pcm chunk 3)>" }

    { "type": "AUDIO", "data": "RU9G" }
    ```

    ## Audio format and chunking

    Use raw PCM audio chunks in each **`data`** message after Base64 decode. For more information, refer to:

    <Icon icon="file-lines" iconType="solid" /> Read [Audio capture best practices](/documentation/how-to/audio-streaming/audio-capture-best-practices) for capture settings and PCM guidance.

    ### Audio streaming recommendations

    <CardGroup cols={2}>
      <Card title="Sample Rate of 16 kHz" icon="waveform">
        Suki streams audio at **16 kHz**, which captures the full range of clinical speech.
      </Card>

      <Card title="Mono Channel" icon="microphone">
        Send a **single mono channel** of audio, not stereo or multi-channel.
      </Card>

      <Card title="LINEAR16 Encoding" icon="file-audio">
        Encode as **LINEAR16** (PCM signed 16-bit little-endian). Remove WAV headers or decode to raw PCM before you send.
      </Card>

      <Card title="Audio Chunk Size of 100 ms" icon="clock">
        Suki supports **100 ms** chunks to balance recognition quality, latency, and efficiency. At 16 kHz mono 16-bit, that is about **3200 bytes** of raw PCM per chunk.
      </Card>

      <Card title="Stream at Real-Time Speed" icon="gauge">
        Pace audio chunks to match their actual duration and stream at or near real time, rather than sending buffered audio as fast as possible.
      </Card>
    </CardGroup>

    ### PCM vs WAV

    Raw PCM is audio data without a file container. **`.wav`** is a container format and includes headers.

    If your source is WAV, remove the **44-byte** RIFF header, or decode the file to raw PCM before sending. Sending WAV headers as PCM reduces recognition quality and makes debugging harder.

    ### Recommended audio format

    * **Encoding**: LINEAR16, PCM signed 16-bit little-endian
    * **Channels**: Mono
    * **Sample rate**: **16 kHz**

    ### Chunk size

    Suki supports **100 ms audio chunks** to balance recognition quality, latency, and efficiency. At **16 kHz**, mono, **16-bit** audio, each chunk contains about **3200 bytes** of raw PCM before Base64 encoding.

    * Send each chunk as one **`AUDIO`** JSON text frame.
    * Keep chunks at **100 ms** throughout active streaming.
    * Pace chunks at or near real time instead of sending buffered audio as quickly as possible.
    * Send audio at least once every **25 seconds** while the stream is active, or Suki disconnects the stream. When the stream is paused, follow the [`KEEP_ALIVE` guidance](/api-reference/quickstart).

    ### Encode each chunk

    For every **`AUDIO`** message:

    1. Take raw PCM bytes.
    2. Encode the bytes using standard Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)).
    3. Send the encoded string as **`data`**.
  </Tab>

  <Tab title="Dictation /ws/transcribe">
    ## Message format

    Each outbound message is a JSON object with a **`type`** field. For audio chunks, the payload field is **`audioData`**.

    ### Audio chunks

    Send audio with **`type`** set to **`AUDIO`**:

    ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    { "type": "AUDIO", "audioData": "<base64-encoded PCM_S16LE audio>" }
    ```

    The **`audioData`** value must be:

    * Standard Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648))
    * An encoding of the raw **PCM\_S16LE** bytes you intend to send
    * Sent as a JSON string, regardless of the programming language you use

    <Warning>
      Do not use:

      * Hex encoding
      * URL-safe Base64
      * Raw binary inside JSON strings
    </Warning>

    ### End-of-audio message

    When you finish sending audio on the WebSocket, send one **`EVENT`** message with **`event`** set to **`AUDIO_END`**:

    ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    { "type": "EVENT", "event": "AUDIO_END" }
    ```

    This message tells the server that no more audio chunks are coming for that stream.

    <Warning>
      Do not:

      * End Dictation audio with **`{ "type": "AUDIO", "data": "RU9G" }`**. That is the ambient **`/ws/stream`** pattern.
      * Use custom end markers instead of **`AUDIO_END`** or the inbound **`EOF`**. See [Read Dictation transcript frames](/documentation/how-to/audio-streaming/dictation-streaming-transcripts#partial-and-final-transcripts).
      * Use binary signaling in place of the JSON **`AUDIO_END`** message.
    </Warning>

    <Note>
      Dictation **`/ws/transcribe`** does **not** use **`START_TIME`**, does **not** use ambient-style **`AUDIO`** messages with a **`data`** field, and does **not** use the ambient end marker **`RU9G`**. Use **`audioData`** for chunks and **`EVENT`** with **`event`**: **`AUDIO_END`** when you are done sending audio.
    </Note>

    ## Required message order

    For each logical stream of audio on the socket:

    1. Send one or more **`AUDIO`** messages. Each message includes one **`audioData`** chunk.
    2. Send one **`EVENT`** message with **`event`**: **`AUDIO_END`** after the last audio chunk you intend to send on that connection.

    There is **no** **`START_TIME`** step and **no** ambient **`RU9G`** end marker on this endpoint.

    ## Example flow

    This example shows the outbound message sequence for one stream: multiple **`AUDIO`** chunks, followed by **`AUDIO_END`**.

    ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    { "type": "AUDIO", "audioData": "<base64(pcm_s16le chunk 1)>" }
    { "type": "AUDIO", "audioData": "<base64(pcm_s16le chunk 2)>" }

    { "type": "EVENT", "event": "AUDIO_END" }
    ```

    ## Audio format and chunking

    Use raw **PCM\_S16LE** audio chunks in each **`audioData`** message after Base64 decode.

    ### Audio streaming recommendations

    <CardGroup cols={2}>
      <Card title="Sample Rate of 16 kHz" icon="waveform">
        Stream Dictation audio at **16 kHz**, which matches the capture rate used in the Dictation streaming examples.
      </Card>

      <Card title="Mono Channel" icon="microphone">
        Send a **single mono channel** of audio, not stereo or multi-channel.
      </Card>

      <Card title="PCM_S16LE Encoding" icon="file-audio">
        Encode as **PCM\_S16LE** (PCM signed 16-bit little-endian). Remove WAV headers or decode to raw PCM before you send.
      </Card>

      <Card title="About 100 ms per Chunk" icon="clock">
        For **16 kHz**, mono, **16-bit** audio, about **3200 bytes** per chunk is about **100 ms** of audio. Size chunks to your capture pipeline if your encoder differs.
      </Card>

      <Card title="Stream at Real-Time Speed" icon="gauge">
        Pace audio chunks to match their actual duration and stream at or near real time, rather than sending buffered audio as fast as possible.
      </Card>
    </CardGroup>

    ### PCM vs WAV

    **PCM\_S16LE** is raw audio data. **`.wav`** is a container format and usually includes a header before the audio data.

    If your source is WAV, skip the **44-byte** header before chunking, or decode the file to raw PCM before sending. Use **`0`** as the offset if your buffer is already raw PCM. Sending WAV headers as PCM reduces recognition quality and makes debugging harder.

    ### Recommended audio format

    * **Encoding**: PCM\_S16LE, PCM signed 16-bit little-endian, same family as **LINEAR16** at **16 kHz** mono in typical capture pipelines
    * **Channels**: Mono
    * **Sample rate**: **16 kHz**, aligned with what your integration expects

    ### Chunk size

    Send audio in small chunks during streaming. For **16 kHz**, mono, **16-bit** audio, about **3200 bytes** per chunk is a common choice, which is about **100 ms** per message. Size chunks to your capture pipeline if your encoder differs.

    ### Encode each chunk

    For every **`AUDIO`** message:

    1. Take raw **PCM\_S16LE** bytes.
    2. Encode the bytes using standard Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)).
    3. Send the encoded string as **`audioData`**.
  </Tab>
</Tabs>

## Related topics

<CardGroup cols={2}>
  <Card title="Stream Ambient Audio" icon="waveform" href="/documentation/how-to/audio-streaming/ambient-audio-streaming" arrow={true}>
    How ambient streaming works, JSON text rules, and links into this wire format.
  </Card>

  <Card title="Stream Dictation Audio" icon="waveform" href="/documentation/how-to/audio-streaming/dictation-streaming" arrow={true}>
    How Dictation streaming works, prerequisites, and links into this wire format.
  </Card>

  <Card title="Audio Capture Best Practices" icon="microphone" href="/documentation/how-to/audio-streaming/audio-capture-best-practices" arrow={true}>
    Capture format, environment, gain, and network readiness before you stream.
  </Card>

  <Card title="Dictation Transcript Frames" icon="message" href="/documentation/how-to/audio-streaming/dictation-streaming-transcripts" arrow={true}>
    Inbound partial and final transcript frames for `/ws/transcribe`.
  </Card>

  <Card title="Complete the Session" icon="circle-check" href="/documentation/how-to/audio-streaming/websocket-streaming-complete-session" arrow={true}>
    Close the socket, end with REST, and troubleshoot common errors.
  </Card>
</CardGroup>

## Next steps

<Icon icon="file-lines" iconType="solid" /> Follow [Complete the session after streaming](/documentation/how-to/audio-streaming/websocket-streaming-complete-session) to close the socket, end the session, and troubleshoot errors.

<Icon icon="file-lines" iconType="solid" /> Copy the [Ambient WebSocket client code example](/documentation/tutorials/ambient-websocket-code-example) for a full client implementation.

<Icon icon="file-lines" iconType="solid" /> Review [Audio capture and streaming FAQs](/api-reference/faqs/audio-capture-streaming) for codec, chunk size, and reconnect guidance.
