WebSocket Fundamentals
HTTP is a request-response protocol: the client asks, the server answers, the connection closes. That model works perfectly for loading pages, but it breaks down the moment you need a server to push data unprompted, whether that is a chat message, a live document cursor, or a dashboard metric changing in real time. WebSocket solves this by upgrading a normal HTTP connection into a persistent, full-duplex channel where either side can send a frame at any moment. Understanding exactly how that works is the foundation for every realtime engineering pattern in this cluster.
This is the first guide in the Interactive Web cluster, which covers realtime frontend engineering. It connects to the transport comparison in realtime systems.
What you'll learn
The HTTP upgrade handshake
WebSocket begins life as an ordinary HTTP request. The protocol upgrade is negotiated in the headers of that first request and its response.
From HTTP to a persistent TCP channel
/* Client sends an HTTP/1.1 upgrade request */
GET /realtime HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
/* Server responds with 101 Switching Protocols */
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
The Sec-WebSocket-Key is a random base64 value the client generates per connection. The server hashes it with a fixed GUID and returns the result as Sec-WebSocket-Accept, which the client verifies to confirm it is talking to a real WebSocket server rather than a misconfigured proxy. After the 101 response, the TCP connection that carried the HTTP exchange stays open and switches to the WebSocket framing protocol. HTTP headers are never sent again on this connection.
The browser WebSocket API
The browser exposes WebSocket as a simple event-driven object. Four events cover the entire connection lifecycle.
Open, message, error, close
var ws = new WebSocket('wss://example.com/realtime');
ws.addEventListener('open', function () {
console.log('connected');
ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
});
ws.addEventListener('message', function (event) {
var msg = JSON.parse(event.data);
handleMessage(msg);
});
ws.addEventListener('error', function (event) {
console.error('WebSocket error', event);
});
ws.addEventListener('close', function (event) {
console.log('closed', event.code, event.reason);
scheduleReconnect();
});
Always use wss:// in production. The wss scheme is WebSocket over TLS, equivalent to HTTPS for ordinary requests. Unencrypted ws:// connections are intercepted by proxies that understand HTTP but not WebSocket frames, causing silent failures. The close event's code field is a standard numeric code: 1000 is a clean close, 1006 means the connection dropped without a close frame, and codes above 4000 are application-defined.
Message framing and subprotocols
WebSocket sends messages as frames. Understanding the framing model helps you design a robust message format before you write the first handler.
Text, binary, and a shared message contract
// Sending typed messages with a shared envelope
function send(ws, type, payload) {
ws.send(JSON.stringify({ type: type, payload: payload, ts: Date.now() }));
}
// Receiving and routing by type
ws.addEventListener('message', function (event) {
var msg = JSON.parse(event.data);
switch (msg.type) {
case 'cursor': updateCursor(msg.payload); break;
case 'patch': applyPatch(msg.payload); break;
case 'presence': updatePresence(msg.payload); break;
}
});
The WebSocket protocol carries text frames, binary frames, ping frames, pong frames, and close frames. Application messages ride in text or binary frames. A typed envelope with a type discriminator and a payload is the standard pattern for routing messages on the client, because a single WebSocket connection typically carries many kinds of events. Subprotocols, declared in the Sec-WebSocket-Protocol header, let you negotiate a named schema like json-v1 between client and server so both sides agree on the message format before the first frame arrives.
Heartbeats and connection health
NAT routers, load balancers, and proxies silently drop idle TCP connections after a timeout, sometimes as short as 30 seconds. A heartbeat prevents that.
Ping-pong to detect silent drops
var HEARTBEAT_MS = 25000;
var pingTimer;
ws.addEventListener('open', startHeartbeat);
ws.addEventListener('close', stopHeartbeat);
function startHeartbeat() {
pingTimer = setInterval(function () {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, HEARTBEAT_MS);
}
function stopHeartbeat() {
clearInterval(pingTimer);
}
The WebSocket protocol has built-in ping and pong control frames that the server can send; browsers handle pong responses automatically without application code. In practice, most teams implement application-level heartbeats as above because they are easier to instrument and can carry a timestamp the server can use to measure round-trip latency. If a pong does not arrive within a second or two of the ping, the connection is dead and should be closed explicitly before reconnecting.
Reconnection with backoff
WebSocket connections drop. Networks fail, servers restart, load balancers time out. The client must reconnect automatically and should not hammer the server when it does.
Exponential backoff with jitter
var attempt = 0;
var MAX_DELAY = 30000;
function connect() {
var ws = new WebSocket('wss://example.com/realtime');
ws.addEventListener('open', function () { attempt = 0; });
ws.addEventListener('close', function (event) {
if (event.code === 1000) return; // intentional close
var delay = Math.min(1000 * Math.pow(2, attempt), MAX_DELAY);
delay += Math.random() * 1000; // jitter
attempt++;
setTimeout(connect, delay);
});
}
connect();
Exponential backoff doubles the wait time on each failure, capped at a maximum. The jitter, a random addition up to one second, prevents the thundering-herd problem where every client that lost the connection reconnects at exactly the same moment after a server restart. This pattern appears in every production WebSocket client, whether hand-rolled or inside a library like Socket.IO or Phoenix Channels.
Choosing WebSocket vs alternatives
WebSocket is not always the right transport, and choosing it when something simpler would work adds complexity without benefit.
WebSocket, SSE, and polling compared
Use WebSocket when you need bidirectional messaging: the client sends data as well as receives it, the connection is long-lived, and latency matters. Collaborative editing, presence systems, and multiplayer state sync all fit. Use Server-Sent Events when data flows only from server to client, the content is text, and you want built-in browser reconnection without application code. Live feeds, notification streams, and log tails are natural SSE use cases. Use HTTP polling when updates are infrequent, the team wants simple request-response semantics, and the few-second delay is acceptable. Each realtime system topic in this cluster builds on the transport choice made here.
Frequently Asked Questions
What is WebSocket?
WebSocket is a protocol that establishes a persistent, full-duplex TCP connection between a browser and a server over a single HTTP upgrade handshake. Once connected, either side can send frames at any time without the overhead of repeated HTTP requests.
How is WebSocket different from HTTP polling?
HTTP polling sends a new request on a timer, paying connection overhead on every interval whether or not there is new data. WebSocket opens once and keeps the connection alive, so the server can push data the instant it is available with no repeated handshake cost.
What are WebSocket heartbeats?
Heartbeats are periodic ping frames sent by either side to confirm the connection is still alive. The receiver responds with a pong frame. If no pong arrives within a timeout, the client treats the connection as dead and reconnects. This prevents silent connection drops from going undetected.
When should I use Server-Sent Events instead of WebSocket?
Server-Sent Events are a good choice when data flows only from server to client and you do not need to send messages back over the same connection. SSE uses standard HTTP, works through proxies that sometimes block WebSocket upgrades, and reconnects automatically. Use WebSocket when you need bidirectional messaging.
Read next: the Interactive Web hub for the full cluster, or continue to realtime systems for the architecture patterns built on top of this transport.
Want to add realtime to a Backbone app?
The Backbone guide covers the model and collection layer that WebSocket messages update.
Explore the Backbone Guide →