Claude API WebSocket Streaming Implementation Guide
If you're searching for a "Claude API WebSocket" endpoint, the short answer is: it doesn't exist. Anthropic's Claude API streams tokens over HTTP using Server-Sent Events (SSE), not WebSockets. There is no wss:// endpoint to connect to, no socket handshake, no bidirectional protocol built into the API itself.
What most developers actually want when they search this phrase is a way to get Claude's streamed output into a system that's already built around WebSockets — a real-time chat app, a game backend, a mobile client using socket.io, or infrastructure that multiplexes many concurrent conversations over a single connection. That's a solvable, common problem: you build a thin WebSocket layer on your own server that consumes the SSE stream from Claude and re-broadcasts it to your WebSocket clients. This article walks through that implementation.
Why Claude Doesn't Expose WebSockets Natively
SSE is a better fit than WebSockets for one-directional, request-triggered streaming like an LLM completion:
- The client makes one HTTP request, the server keeps it open and pushes chunks.
- It reuses standard HTTP infrastructure — proxies, load balancers, CDNs — without special upgrade handling.
- It auto-reconnects at the browser level with
EventSource, and is trivial to consume withfetch+ a readable stream in Node.js. - There's no need for bidirectional messaging mid-generation; the client isn't sending data back to Claude while tokens stream out.
WebSockets add value when you need full-duplex communication or want to fan a single upstream stream out to many downstream consumers (e.g., broadcasting one generation to multiple viewers, or maintaining persistent connections for a real-time multiplayer app). That's an application-layer concern, not something the model API needs to solve for you.
The Architecture: SSE In, WebSocket Out
The pattern is straightforward:
- Your backend opens an SSE connection to Claude (or a gateway like SubToAPI) using standard streaming.
- As chunks arrive, you parse the SSE events and forward the relevant deltas to connected WebSocket clients.
- You manage connection lifecycle, backpressure, and error handling on your own server rather than relying on the model API for it.
This keeps your API key server-side, lets you fan out one generation to multiple clients, and gives you full control over framing and reconnection logic.
Server: Bridging SSE to WebSocket in Node.js
import { WebSocketServer } from 'ws';
import http from 'http';
const server = http.createServer();
const wss = new WebSocketServer({ server });
wss.on('connection', (ws) => {
ws.on('message', async (raw) => {
const { prompt } = JSON.parse(raw.toString());
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.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\n');
buffer = lines.pop();
for (const line of lines) {
const dataLine = line.split('\n').find(l => l.startsWith('data:'));
if (!dataLine) continue;
const payload = dataLine.replace('data:', '').trim();
if (payload === '[DONE]') {
ws.send(JSON.stringify({ type: 'done' }));
continue;
}
const event = JSON.parse(payload);
if (event.type === 'content_block_delta') {
ws.send(JSON.stringify({
type: 'delta',
text: event.delta?.text ?? ''
}));
}
}
}
});
});
server.listen(3001);
This gives your frontend a single persistent socket where it sends a prompt and receives incremental delta messages, regardless of how the upstream API actually streams data. If you swap providers or gateways later, only this bridge changes — your client code stays the same.
Client: Consuming the WebSocket
const socket = new WebSocket('wss://your-server.example/stream');
socket.onopen = () => {
socket.send(JSON.stringify({ prompt: 'Summarize this in 3 bullet points.' }));
};
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'delta') {
appendToUI(msg.text);
} else if (msg.type === 'done') {
finalizeUI();
}
};
Handling Reconnection and Backpressure
A few things you need to handle yourself once you're running this bridge in production:
- Reconnection: unlike
EventSource, raw WebSockets don't auto-reconnect. Implement exponential backoff on the client and consider replaying the last N tokens on reconnect so users don't lose partial output. - Backpressure: if a WebSocket client is slow to consume,
ws.send()can buffer indefinitely. Monitorws.bufferedAmountand drop or pause the upstream read if it grows too large. - Multiple clients per generation: if you're fanning one Claude response out to several viewers (e.g., a shared session), keep a single upstream SSE connection and broadcast to a set of sockets rather than opening one upstream request per client — this saves both latency and cost.
- Timeouts: long generations can run for tens of seconds. Make sure your load balancer and any reverse proxy in front of the WebSocket server has appropriate idle timeouts configured, since WS connections behave differently from typical HTTP under most proxy defaults.
Where SubToAPI Fits
If you're already sending requests through SubToAPI, the SSE stream your bridge consumes is the same messages endpoint documented at /docs/messages and /docs/streaming — application keys (sub_live_...), usage metadata, and tool calls all work the same way whether you're consuming the stream directly in a browser or relaying it through your own WebSocket layer as shown above. This is useful if your team wants centralized key management and usage visibility while still building custom real-time infrastructure on top. Get started at /signup or check /docs/quickstart for the basic request format before wiring up the bridge.
questions
Does Claude's API support WebSockets directly? No. Claude streams responses over HTTP using Server-Sent Events. To get WebSocket behavior, you build a bridge server that consumes the SSE stream and forwards messages to WebSocket clients.
Why would I want WebSockets if SSE already streams tokens? WebSockets make sense when you need bidirectional communication, want to fan one generation out to multiple connected clients, or your existing infrastructure (mobile apps, real-time backends) is already built around persistent sockets rather than HTTP streaming.
What's the biggest pitfall when building this bridge myself? Backpressure and reconnection. Slow WebSocket clients can buffer memory indefinitely if you don't monitor bufferedAmount, and unlike EventSource, raw WebSockets require you to implement your own reconnect and replay logic.