← Blog

Claude API Server-Sent Events Streaming Explained

2026-09-28 · 4 min read · SubToAPI Team

When you stream a response from the Claude API, you're not getting a WebSocket connection or a custom binary protocol — you're getting Server-Sent Events (SSE), a standard HTTP mechanism for pushing a sequence of text events from server to client over a single long-lived connection. If you're trying to understand how that stream is structured, what event types Claude sends, and how to parse them without a library, this is what you need to know.

SSE is old, boring, and reliable: it's just HTTP with Content-Type: text/event-stream, the connection stays open, and the server writes data: ... lines as they become available. No handshake negotiation, no binary framing, no extra ports to open. That's exactly why Claude (and most LLM APIs) use it for streaming — it works through standard proxies, load balancers, and browser fetch/EventSource implementations without special infrastructure.

What an SSE stream from Claude actually looks like

Set "stream": true in your request body and the response body becomes a sequence of data: lines instead of a single JSON payload. A raw chunk looks like this:

event: message_start
data: {"type":"message_start","message":{"id":"msg_01...","role":"assistant","content":[]}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":", world"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":12}}

event: message_stop
data: {"type":"message_stop"}

Each event is a named type followed by a JSON payload, separated by a blank line. There's no ambiguity about where one event ends and the next begins — the blank line is the delimiter, which is the whole point of the SSE spec.

The event types you'll see

Claude's stream always follows the same lifecycle:

If you're implementing tool use over streaming, input_json_delta events give you the tool arguments as a fragmented JSON string — you have to buffer and concatenate the fragments per index before parsing the final JSON. This is covered in more depth in /docs/tools, but the core SSE mechanics are the same as text streaming.

Parsing SSE manually

If you're not using an SDK, parsing SSE by hand is straightforward but has a few gotchas:

const response = await fetch('https://api.anthropic.com/v1/messages', {
  method: 'POST',
  headers: {
    'x-api-key': process.env.ANTHROPIC_API_KEY,
    'anthropic-version': '2023-06-01',
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    model: 'claude-sonnet-4-5',
    max_tokens: 1024,
    stream: true,
    messages: [{ role: 'user', content: 'Write a haiku about SSE.' }],
  }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split('\n');
  buffer = lines.pop(); // keep incomplete line for next chunk

  for (const line of lines) {
    if (line.startsWith('data: ')) {
      const payload = JSON.parse(line.slice(6));
      if (payload.type === 'content_block_delta') {
        process.stdout.write(payload.delta.text ?? '');
      }
    }
  }
}

The common mistakes here: assuming each network chunk is exactly one event (it isn't — chunks can split mid-event or contain multiple events), forgetting to buffer partial lines, and not handling the ping and error event types explicitly. Browsers offer a native EventSource API, but it only supports GET requests and can't send custom headers or a JSON body, so it's not usable directly against Claude's POST-based streaming endpoint — you need fetch with a readable stream reader, or a library that wraps this for you.

Why teams put a layer in front of raw SSE

Raw SSE parsing works fine for a single client, but once you're serving streamed responses to multiple users from your own backend, you run into the same operational problems every LLM integration eventually hits: reconnect logic when a stream drops mid-response, per-application usage accounting from message_delta token counts, and rotating credentials without redeploying every client.

SubToAPI sits in front of your Claude access and re-exposes it as sub_live_... API keys with the same SSE streaming contract, so your existing parsing code doesn't change — you just point it at https://api.subtoapi.app/v1/messages instead. You also get per-key usage metadata and team seats, which matters once more than one person or service is consuming the stream. See /docs/streaming for the exact endpoint and headers, or /docs/quickstart to get a key from /signup.

questions

Is Claude's streaming API WebSockets or SSE? It's SSE (Server-Sent Events) over standard HTTP with Content-Type: text/event-stream. There's no WebSocket endpoint — you make a normal POST request with "stream": true and read the response body as a stream.

Can I use the browser's native EventSource for Claude streaming? No. EventSource only supports GET requests without custom headers, but Claude's endpoint requires a POST with a JSON body and an API key header. Use fetch with a ReadableStream reader instead, or an SDK that handles this.

What happens if the SSE connection drops mid-response? You'll stop receiving content_block_delta events without a message_stop. Your client should detect the incomplete stream (e.g., via a timeout) and either resume with a fresh request or fall back to non-streaming mode — Claude's API doesn't support resuming a partial stream from a cursor.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →