> ## 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 Disconnects During Ambient Streaming

> Fix ambient WebSocket drops during live audio on GET /ws/stream

Ambient streaming opens a WebSocket on **`GET /ws/stream`** after you create a session. Unexpected closes usually come from auth, frame format, keep-alives, or ending the stream in the wrong order. Closing the socket alone does not end the ambient session or start note generation.

## Common causes

* Opening `/ws/stream` before create succeeds, or after the session leaves **`CREATED`** (handshake returns **`FailedPrecondition`**).
* Bad handshake auth (wrong `Sec-WebSocket-Protocol` order or headers).
* Binary frames, non-JSON payloads, or multiple JSON objects in one frame.
* No audio for **25 seconds** while the stream is active, or no **`KEEP_ALIVE`** while paused.
* Missing **`RU9G`**, or calling REST end without the documented end sequence.

## Fix

<Steps>
  <Step title="Confirm Create and Session State">
    Create the ambient session first and store `ambient_session_id`. Open `/ws/stream` only while status is **`CREATED`**. Reconnect with the same ID only while status stays **`CREATED`**. Otherwise the handshake returns **`FailedPrecondition`**.
  </Step>

  <Step title="Authenticate the Handshake">
    In the browser, set `Sec-WebSocket-Protocol` to `SukiAmbientAuth,<sdp_suki_token>,<ambient_session_id>` (token before session ID). Non-browser clients use the documented Ambient WebSocket upgrade headers.
  </Step>

  <Step title="Send JSON Text Frames in Order">
    Send one UTF-8 JSON object per text frame. Order per segment: **`START_TIME`** → Base64 LINEAR16 PCM in **`AUDIO`** / **`data`** → optional **`EVENT`** → final **`AUDIO`** with **`"data": "RU9G"`**. Do not send binary audio frames.
  </Step>

  <Step title="Keep the Connection Alive">
    While audio is flowing, send audio at least every **25 seconds**. While paused, send `{"type":"EVENT","event":"KEEP_ALIVE"}` at least every **5 seconds**. Ambient supports pauses of up to **30 minutes** when keep-alives continue.
  </Step>

  <Step title="End in Order">
    After the last PCM chunk, send **`RU9G`**, close the WebSocket, then call REST end. Then poll status before you retrieve notes.
  </Step>
</Steps>

```typescript theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
// After the last PCM AUDIO frame on an open ambient WebSocket:
ws.send(JSON.stringify({ type: "AUDIO", data: "RU9G" }));
ws.close();

const endRes = await fetch(
  `${BASE_URL}/api/v1/ambient/session/${ambientSessionId}/end`,
  {
    method: "POST",
    headers: {
      sdp_suki_token: SDP_SUKI_TOKEN,
      sdp_provider_id: SDP_PROVIDER_ID,
    },
  }
);
if (!endRes.ok) {
  throw new Error(`End ambient session failed: ${endRes.status}`);
}
```

<Tip>
  Do not send Dictation-style **`AUDIO_END`** on ambient `/ws/stream`. Use **`RU9G`**. See [Ambient streaming wire format](/documentation/how-to/audio-streaming/websocket-streaming-wire-format-ambient).
</Tip>

| Problem | Why it happens | How to fix it |
| :- | :- | :- |
| Server returns JSON parse errors | Binary frame, non-JSON payload, or multiple JSON objects in one frame | Send one UTF-8 JSON object per WebSocket text frame |
| Audio ignored or poor transcription | Hex, URL-safe Base64, or WAV headers treated as PCM | Encode raw LINEAR16 PCM with standard Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648)) in **`data`** |
| Session never completes | Missing end-of-stream marker | Final **`AUDIO`** with **`"data": "RU9G"`**, then close, then REST end |
| Disconnect during a long pause | No messages before keep-alive timeout | Send **`KEEP_ALIVE`** at least every **5 seconds** while paused |
| Unable to reconnect | Session left **`CREATED`** | Reconnect only while status is **`CREATED`** |

## Next steps

<Icon icon="file-lines" iconType="solid" /> **[End ambient after streaming](/documentation/cookbooks/end-ambient-after-streaming)** - RU9G, close socket, then REST end

<Icon icon="file-lines" iconType="solid" /> **[Empty notes after end session](/documentation/troubleshooting/empty-notes-after-end-session)** - Poll status before content APIs

<Icon icon="file-lines" iconType="solid" /> **[WAV header streamed as audio](/documentation/troubleshooting/wav-header-streamed-as-audio)** - Strip RIFF headers before Base64 PCM

<Icon icon="file-lines" iconType="solid" /> **[Build an ambient streaming client](/documentation/tutorials/ambient-websocket-code-example)** - End-to-end create, stream, and retrieve

<Icon icon="file-lines" iconType="solid" /> **[Dictation WebSocket handshake](/documentation/troubleshooting/dictation-websocket-handshake)** - Different endpoint and wire format
