Claude API Streaming with WebSockets: A Practical Tutorial
What "Claude API streaming with WebSockets" actually means
If you searched for this, you probably want one of two things: either you're building a chat UI and want token-by-token responses pushed to the browser in real time, or you've assumed Claude's API streams over WebSockets and you're trying to find the connection code. It's worth clearing this up immediately, because it changes how you build the feature.
Anthropic's Claude API — and SubToAPI's HTTPS layer on top of it — streams responses using Server-Sent Events (SSE), not raw WebSockets. SSE is a one-way, HTTP-based streaming protocol: you open a connection, the server pushes chunks of text as they're generated, and the connection closes when the response finishes. There is no native Claude WebSocket endpoint. If you need WebSockets specifically — for example because your frontend architecture already uses a WebSocket gateway, or you want bidirectional communication for a multi-turn chat app — you build a small bridge server that consumes the SSE stream from Claude and re-broadcasts it to clients over a WebSocket. This tutorial shows exactly how to do that.
Why SSE and not WebSockets
SSE is simpler for this use case because the data flow is one-directional: you send a prompt, you get a stream of text chunks back. There's no need for the client to send messages mid-stream. SSE also works over plain HTTPS, requires no special server upgrade handshake, and is natively supported by fetch and EventSource in browsers. WebSockets add complexity (connection lifecycle, ping/pong, reconnection logic) that isn't needed just to receive a one-way stream of tokens.
That said, WebSockets make sense when:
- You already have a WebSocket-based real-time layer (game state, collaborative editing, live dashboards) and want Claude responses to flow through the same channel.
- You need to multiplex multiple concurrent Claude streams to one client over a single connection.
- Your client environment doesn't support SSE well (some embedded or native mobile stacks).
Step 1: Stream from Claude over SSE on the server
Whether you call the Anthropic API directly or go through SubToAPI's compatible endpoint, the server-side streaming call looks the same shape — a POST request with "stream": true, consumed as a chunked response.
// server.js
import fetch from "node-fetch";
async function streamClaudeResponse(prompt, onChunk) {
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
stream: true,
messages: [{ role: "user", content: prompt }],
}),
});
const reader = response.body;
for await (const chunk of reader) {
const lines = chunk.toString("utf8").split("\n").filter(Boolean);
for (const line of lines) {
if (line.startsWith("data:")) {
const payload = line.replace("data:", "").trim();
if (payload === "[DONE]") return;
onChunk(JSON.parse(payload));
}
}
}
}
This is the same SSE parsing pattern you'd use with the raw Anthropic API — SubToAPI keeps the request and response shape compatible so existing streaming code doesn't need rewriting. Full request/response details are in the streaming docs and the general Messages API reference.
Step 2: Bridge the stream to a WebSocket server
Now wrap that function in a ws server so each connected client gets Claude's output pushed to them in real time.
// ws-bridge.js
import { WebSocketServer } from "ws";
import { streamClaudeResponse } from "./server.js";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (socket) => {
socket.on("message", async (raw) => {
const { prompt } = JSON.parse(raw.toString());
try {
await streamClaudeResponse(prompt, (event) => {
if (event.type === "content_block_delta") {
socket.send(JSON.stringify({
type: "delta",
text: event.delta?.text ?? "",
}));
}
if (event.type === "message_stop") {
socket.send(JSON.stringify({ type: "done" }));
}
});
} catch (err) {
socket.send(JSON.stringify({ type: "error", message: err.message }));
}
});
});
Each incoming WebSocket message triggers a new SSE call to Claude, and every delta is forwarded over the socket as it arrives. The client never talks to Claude directly — your bridge server holds the API key and does all the parsing.
Step 3: Consume the stream in the browser
const socket = new WebSocket("ws://localhost:8080");
let fullText = "";
socket.onopen = () => {
socket.send(JSON.stringify({ prompt: "Explain event loops in Node.js" }));
};
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "delta") {
fullText += msg.text;
document.getElementById("output").textContent = fullText;
}
if (msg.type === "done") {
console.log("Stream finished");
}
};
This gives you the token-by-token rendering effect users expect from streaming chat, delivered over a WebSocket connection you fully control — including reconnect logic, multiplexing multiple conversations, or combining Claude output with other real-time events in your app.
Handling multiple concurrent streams
If several clients can be streaming at once, keep each socket's state isolated and don't share a single Claude connection across clients. Tag each outgoing WebSocket message with a requestId if a client might fire multiple prompts in parallel, so the frontend can route deltas to the right UI element:
socket.send(JSON.stringify({ type: "delta", requestId, text: event.delta?.text ?? "" }));
On the client, buffer text per requestId instead of a single global string.
Where SubToAPI fits in
If you're already using SubToAPI to turn your Claude access into an HTTPS API with sub_live_... keys, the bridge pattern above drops in directly — swap the endpoint and auth header shown in Step 1, and everything downstream (WebSocket broadcasting, client rendering) stays the same. You get usage metadata per request and team-level key management without changing your streaming architecture. Check the quickstart to get a key, or see pricing for plan details before you sign up.
Questions
Does Claude's API support native WebSocket streaming? No. Claude streams responses via Server-Sent Events over HTTPS. To get WebSocket delivery, you run a small server that consumes the SSE stream and re-broadcasts chunks to clients over WebSockets, as shown above.
Is WebSocket streaming faster than SSE for Claude responses? Not meaningfully — the bottleneck is token generation speed, not transport. Use WebSockets when you need bidirectional communication or already have a WebSocket infrastructure, not for raw speed.
Can I stream directly from the browser without a backend bridge? Technically yes with SSE and fetch, but you'd expose your API key client-side, which is a security risk. Keep the Claude call server-side and only forward parsed text to the browser.