Claude Code WebSocket Implementation (2026)

WebSockets enable true bidirectional communication between clients and servers, making them essential for real-time applications. Unlike traditional HTTP polling, WebSockets maintain a persistent connection, allowing instant event propagation with minimal latency and reduced bandwidth consumption. This guide walks you through implementing production-ready WebSocket systems using Claude Code.

Why WebSockets Over HTTP Polling

HTTP polling creates a new connection for every data request, wasting bandwidth and creating artificial delays. You typically set a polling interval, 5 seconds, 10 seconds, or longer, which means users experience stale data between requests. For every poll, the server processes the request even if nothing has changed.

WebSockets solve this by establishing a single persistent connection. The server pushes updates instantly whenever events occur. This approach is vastly more efficient for applications requiring live notifications, collaborative editing, real-time dashboards, gaming, or chat systems. The initial WebSocket handshake uses standard HTTP, making it compatible with existing infrastructure and proxies.

The trade-off is slightly increased server complexity. You need to manage persistent connections, handle reconnections gracefully, and broadcast messages to multiple clients efficiently. Claude Code excels at generating this infrastructure code.

Setting Up a WebSocket Server

Here’s a production-ready WebSocket server using Node.js with the ws library:

const WebSocket = require('ws');
const http = require('http');
const EventEmitter = require('events');
class WebSocketServer extends EventEmitter {
 constructor(port = 8080) {
 super();
 this.server = http.createServer();
 this.wss = new WebSocket.Server({ server: this.server });
 this.clients = new Set();
 this.port = port;
 this.setupHandlers();
 }
 setupHandlers() {
 this.wss.on('connection', (ws) => {
 console.log('Client connected');
 this.clients.add(ws);
 ws.on('message', (data) => {
 try {
 const message = JSON.parse(data);
 this.handleMessage(ws, message);
 } catch (error) {
 console.error('Invalid message format:', error);
 ws.send(JSON.stringify({
 type: 'error',
 message: 'Invalid message format'
 }));
 }
 });
 ws.on('close', () => {
 console.log('Client disconnected');
 this.clients.delete(ws);
 this.emit('clientDisconnected');
 });
 ws.on('error', (error) => {
 console.error('WebSocket error:', error);
 this.clients.delete(ws);
 });
 });
 }
 handleMessage(ws, message) {
 const { type, payload } = message;
 if (type === 'subscribe') {
 ws.userId = payload.userId;
 ws.topics = payload.topics || [];
 ws.send(JSON.stringify({ type: 'subscribed', topics: ws.topics }));
 } else if (type === 'unsubscribe') {
 ws.topics = ws.topics.filter(t => t !== payload.topic);
 } else {
 this.emit('message', { sender: ws, type, payload });
 }
 }
 broadcast(message, filter = null) {
 const data = JSON.stringify(message);
 this.clients.forEach((client) => {
 if (!filter || filter(client)) {
 if (client.readyState === WebSocket.OPEN) {
 client.send(data);
 }
 }
 });
 }
 broadcastToTopic(topic, message) {
 this.broadcast(message, (client) =>
 client.topics && client.topics.includes(topic)
 );
 }
 start() {
 this.server.listen(this.port, () => {
 console.log(`WebSocket server running on port ${this.port}`);
 });
 }
 stop() {
 this.wss.clients.forEach(client => client.close());
 this.server.close();
 }
}
module.exports = WebSocketServer;

This server handles client connections, message parsing, topic subscriptions, and targeted broadcasting. The Set data structure efficiently tracks active connections.

Client Connection Handling

WebSocket clients need solid connection management, including automatic reconnection on failure:

class WebSocketClient {
 constructor(url, options = {}) {
 this.url = url;
 this.maxRetries = options.maxRetries || 5;
 this.retryDelay = options.retryDelay || 3000;
 this.retryAttempts = 0;
 this.ws = null;
 this.messageQueue = [];
 this.listeners = {};
 this.heartbeatInterval = options.heartbeatInterval || 30000;
 }
 connect() {
 return new Promise((resolve, reject) => {
 try {
 this.ws = new WebSocket(this.url);
 this.ws.onopen = () => {
 console.log('Connected to WebSocket');
 this.retryAttempts = 0;
 this.flushMessageQueue();
 this.startHeartbeat();
 this.emit('connect');
 resolve();
 };
 this.ws.onmessage = (event) => {
 try {
 const message = JSON.parse(event.data);
 this.handleMessage(message);
 } catch (error) {
 console.error('Failed to parse message:', error);
 }
 };
 this.ws.onerror = (error) => {
 console.error('WebSocket error:', error);
 this.emit('error', error);
 reject(error);
 };
 this.ws.onclose = () => {
 console.log('Disconnected from WebSocket');
 this.stopHeartbeat();
 this.emit('disconnect');
 this.attemptReconnect();
 };
 } catch (error) {
 reject(error);
 }
 });
 }
 send(message) {
 if (this.ws && this.ws.readyState === WebSocket.OPEN) {
 this.ws.send(JSON.stringify(message));
 } else {
 this.messageQueue.push(message);
 }
 }
 flushMessageQueue() {
 while (this.messageQueue.length > 0) {
 const message = this.messageQueue.shift();
 this.send(message);
 }
 }
 attemptReconnect() {
 if (this.retryAttempts < this.maxRetries) {
 this.retryAttempts++;
 const delay = this.retryDelay * Math.pow(2, this.retryAttempts - 1);
 console.log(`Reconnecting in ${delay}ms (attempt ${this.retryAttempts})`);
 setTimeout(() => this.connect().catch(console.error), delay);
 } else {
 console.error('Max reconnection attempts reached');
 this.emit('maxRetriesExceeded');
 }
 }
 handleMessage(message) {
 const { type } = message;
 if (this.listeners[type]) {
 this.listeners[type].forEach(callback => callback(message));
 }
 this.emit('message', message);
 }
 on(event, callback) {
 if (!this.listeners[event]) {
 this.listeners[event] = [];
 }
 this.listeners[event].push(callback);
 }
 emit(event, data) {
 if (this.listeners[event]) {
 this.listeners[event].forEach(callback => callback(data));
 }
 }
 startHeartbeat() {
 this.heartbeat = setInterval(() => {
 this.send({ type: 'ping' });
 }, this.heartbeatInterval);
 }
 stopHeartbeat() {
 clearInterval(this.heartbeat);
 }
 disconnect() {
 this.stopHeartbeat();
 if (this.ws) {
 this.ws.close();
 }
 }
}

This client implementation features automatic exponential backoff reconnection, message queuing for offline scenarios, heartbeat monitoring, and event listener patterns.

Event Broadcasting and Room Management

For applications with multiple rooms or channels, implement targeted broadcasting:

class ChatRoom {
 constructor(roomId, server) {
 this.roomId = roomId;
 this.server = server;
 this.members = new Map();
 this.messageHistory = [];
 this.maxHistory = 100;
 }
 addMember(ws, userId, username) {
 this.members.set(ws, { userId, username, joinedAt: Date.now() });
 this.broadcast({
 type: 'userJoined',
 userId,
 username,
 memberCount: this.members.size
 });
 this.sendHistoryTo(ws);
 }
 removeMember(ws) {
 const member = this.members.get(ws);
 if (member) {
 this.members.delete(ws);
 this.broadcast({
 type: 'userLeft',
 userId: member.userId,
 username: member.username,
 memberCount: this.members.size
 });
 }
 }
 handleMessage(ws, content) {
 const member = this.members.get(ws);
 if (!member) return;
 const message = {
 type: 'message',
 userId: member.userId,
 username: member.username,
 content,
 timestamp: Date.now()
 };
 this.messageHistory.push(message);
 if (this.messageHistory.length > this.maxHistory) {
 this.messageHistory.shift();
 }
 this.broadcast(message);
 }
 broadcast(message) {
 const data = JSON.stringify(message);
 this.members.forEach((member, ws) => {
 if (ws.readyState === WebSocket.OPEN) {
 ws.send(data);
 }
 });
 }
 sendHistoryTo(ws) {
 if (ws.readyState === WebSocket.OPEN) {
 ws.send(JSON.stringify({
 type: 'history',
 messages: this.messageHistory
 }));
 }
 }
}

This room manager maintains message history, tracks members, and broadcasts events only to relevant clients.

Error Handling and Reconnection Patterns

Solid error handling distinguishes production systems from prototypes. Handle connection timeouts, invalid messages, and graceful degradation:

class ResilientWebSocketClient extends WebSocketClient {
 constructor(url, options = {}) {
 super(url, options);
 this.requestTimeout = options.requestTimeout || 5000;
 this.pendingRequests = new Map();
 this.requestId = 0;
 }
 requestResponse(message, timeout = this.requestTimeout) {
 return new Promise((resolve, reject) => {
 const requestId = ++this.requestId;
 const timer = setTimeout(() => {
 this.pendingRequests.delete(requestId);
 reject(new Error('Request timeout'));
 }, timeout);
 this.pendingRequests.set(requestId, { resolve, reject, timer });
 this.send({ ...message, requestId });
 });
 }
 handleMessage(message) {
 if (message.requestId && this.pendingRequests.has(message.requestId)) {
 const { resolve, timer } = this.pendingRequests.get(message.requestId);
 clearTimeout(timer);
 this.pendingRequests.delete(message.requestId);
 resolve(message);
 } else {
 super.handleMessage(message);
 }
 }
}

This pattern enables request-response semantics over WebSockets, with automatic timeout handling.

Best Practices and Deployment Considerations

When deploying WebSocket systems, consider these production requirements:

Implement connection limits to prevent resource exhaustion. Track concurrent connections per user and implement circuit breakers when thresholds are exceeded.

Use message compression for bandwidth-heavy applications. Most WebSocket implementations support the permessage-deflate extension automatically.

Monitor connection health with heartbeat mechanisms. Detect stale connections that appear open but aren’t truly responsive.

Implement message acknowledgment systems for critical updates. Have clients confirm receipt of important events, with server-side retry logic for unacknowledged messages.

Scale horizontally with sticky sessions or Redis-backed message brokers. When traffic exceeds single-server capacity, use message queues to broadcast across multiple server instances.

Secure WebSocket connections with WSS (WebSocket Secure) and implement authentication at the handshake stage. Validate user permissions before subscribing to channels.

Test reconnection logic thoroughly. Simulate network partitions, server crashes, and connection timeouts to ensure your error handling works as designed.

Step-by-Step Guide: Building a Production WebSocket System

Here is a concrete approach to building a production-ready real-time event system with Claude Code.

Step 1. Choose your transport layer. Decide between raw WebSockets, Socket.IO (which adds rooms, namespaces, and auto-reconnect), or Server-Sent Events (SSE) for one-way push. Claude Code explains the trade-offs for your specific use case. SSE is simpler for dashboards that only receive data, while raw WebSockets are better when clients also send events. For most applications, raw ws with a custom client wrapper strikes the best balance between control and simplicity.

Step 2. Define your message protocol. Before writing server code, define the message envelope format your system will use. A typed discriminated union in TypeScript ensures every message has a type field, a requestId for request-response correlation, and a payload with a shape specific to each type. Claude Code generates the full TypeScript type definitions and the corresponding JSON schema for validation.

Step 3. Implement the server with connection lifecycle tracking. The production server needs to track not just the raw WebSocket connections but also the user identity, subscription topics, and last activity timestamp for each connection. Claude Code generates the connection registry with proper cleanup on close events and a garbage collection pass that removes stale connections that did not close cleanly.

Step 4. Add authentication at the handshake stage. Authenticate clients before upgrading the HTTP connection to WebSocket. Parse a JWT or session token from the query string or Sec-WebSocket-Protocol header during the upgrade event. Claude Code generates the authentication middleware that rejects unauthorized connections with a 401 before the WebSocket handshake completes, keeping your event stream private.

Step 5. Deploy behind a load balancer with sticky sessions. When scaling horizontally, each WebSocket connection must route consistently to the same server instance. Configure your load balancer (NGINX, AWS ALB, or Cloudflare) with sticky sessions based on the client IP or a session cookie. For stateless broadcasting across instances, Claude Code generates the Redis pub/sub bridge that allows any server instance to broadcast to connections held by other instances.

Common Pitfalls

Not handling the CLOSING ready state. Between OPEN and CLOSED, there is a brief CLOSING state where the connection has been asked to close but has not finished. Attempting to send() during this window throws an error. Claude Code generates a defensive send helper that checks readyState === WebSocket.OPEN before every write.

Memory leaks from uncleared event listeners. Each time a client reconnects, new event listeners are attached. Without removing the old listeners on close, your server accumulates thousands of listeners per client over time. Claude Code generates cleanup functions that remove all listeners during the close event handler.

Broadcasting to disconnected clients. The clients Set or Map on your server can contain WebSocket objects whose readyState has moved to CLOSED if the garbage collection logic is slow. Sending to a closed socket throws silently in some libraries and loudly in others. Always check readyState === WebSocket.OPEN before calling send() in broadcast loops.

Not implementing a message size limit. Clients can send arbitrarily large messages. Without a maxPayload limit on your ws.Server configuration, a malicious or buggy client can exhaust server memory with a single oversized message. Claude Code generates the server configuration with sensible payload limits and a handler that closes the connection when the limit is exceeded.

Exposing internal errors in error messages sent to clients. When your server catches an exception while processing a client message, do not include the full stack trace or internal error details in the response. Log the full error server-side and send the client only a sanitized error code. Claude Code generates the error serialization layer that maps internal exceptions to client-safe error codes.

Best Practices

Use binary messages for high-frequency updates. For applications that push many small updates per second (trading tickers, game state, sensor readings), JSON text messages carry significant overhead from field name serialization. MessagePack or Protocol Buffers reduce message size by 30-60%. Claude Code generates the serialization layer and the TypeScript decoder for the client side.

Implement back-pressure on the server. If a slow client is not reading messages fast enough, its send buffer fills up. Continuing to push messages causes memory growth and eventually crashes the server. Claude Code generates a per-connection send queue with a maximum depth, dropping or rejecting new messages when the queue is full and emitting a metric so you can monitor slow consumers.

Version your WebSocket protocol. Include a protocol version in the initial handshake so you can evolve message formats without breaking existing clients. Claude Code generates the version negotiation handshake and a compatibility layer that translates between protocol versions during the transition period when old and new clients coexist.

Test reconnection under realistic conditions. Use toxiproxy or tc netem to simulate packet loss, high latency, and connection drops during your integration tests. Verify that your client reconnects correctly, re-subscribes to topics, and does not miss events that occurred during the disconnect window. Claude Code generates the test scaffold for each failure scenario.

Integration Patterns

React and Next.js client integration. Claude Code generates a custom React hook (useWebSocket) that manages the connection lifecycle, handles reconnection, and exposes typed message streams as React state. The hook integrates with React’s cleanup mechanism to close the connection when the component unmounts, preventing memory leaks in single-page applications.

Redis pub/sub for horizontal scaling. When your application runs on multiple servers, a client connected to server A cannot receive events published by code running on server B. Claude Code generates the Redis adapter that subscribes to a shared channel and re-publishes messages to local WebSocket connections, enabling stateless horizontal scaling.

Event sourcing integration. For applications built on event sourcing, Claude Code generates the WebSocket gateway that subscribes to your event store’s change stream and pushes relevant events to subscribed clients in real time, replacing polling-based refresh patterns with instant push notifications.



I'm a solo developer in Vietnam. 50K Chrome extension users. $500K+ on Upwork. 5 Claude Max subscriptions running agent fleets in parallel. These are my actual CLAUDE.md templates, orchestration configs, and prompts. Not a course. Not theory. The files I copy into every project before I write a line of code. **[See what's inside →](https://zovo.one/lifetime?utm_source=ccg&utm_medium=cta-default&utm_campaign=claude-code-websocket-implementation-real-time-events-guide)** $99 once. Free forever. 47/500 founding spots left.

Related Reading

Built by theluckystrike. More at zovo.one

Find the right skill → Browse 155+ skills in our Skill Finder.

See Also

Try it: Paste your error into our Error Diagnostic for an instant fix.