Guide
WebSockets and server-sent events explained
Most web pages still load with a single HTTP request and response. That model is simple, cache-friendly, and scales effortlessly — until you need live updates: a chat message arriving while you type, a price tick on a dashboard, or a game state changing every frame. Browsers offer three main patterns beyond plain REST: polling, server-sent events (SSE), and WebSockets. Picking the wrong one wastes battery, burns server CPU, or breaks silently behind a corporate proxy.
The four ways to get "live" data
Before jumping to WebSockets, map your traffic shape. Real-time is not one feature — it is a spectrum from "good enough within five seconds" to "sub-50 ms bidirectional."
-
Short polling — the client repeatedly calls
GET /updatesevery N seconds. Easiest to implement and works through every firewall, but wastes requests when nothing changed and adds latency up to the poll interval. - Long polling — the server holds the HTTP connection open until an event arrives (or a timeout), then the client immediately opens a new request. Better than short polling for sparse events, but each message still pays HTTP overhead and connection setup.
-
Server-sent events (SSE) — one long-lived HTTP response where the
server pushes
text/event-streamframes to the browser. NativeEventSourceAPI, automatic reconnect withLast-Event-ID, works over HTTP/2. Server to client only. -
WebSockets — upgrade an HTTP connection to a persistent,
full-duplex channel (
ws://orwss://). Both sides send binary or text frames with minimal framing overhead. Required when the client must push frequently (gaming input, collaborative cursors, trading orders).
Rule of thumb: if updates flow mostly one direction (notifications, live logs, sports scores), start with SSE. If both directions are chatty or you need binary frames, use WebSockets. If updates are rare and latency tolerance is seconds, polling may be fine — and it benefits from ordinary HTTP caching patterns on read endpoints.
How the WebSocket handshake works
WebSockets do not replace HTTP — they begin as HTTP. The client sends a normal GET with upgrade headers:
GET /socket HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
If the server agrees, it responds 101 Switching Protocols with a
Sec-WebSocket-Accept header derived from the key plus a fixed GUID —
proof both sides speak WebSocket. After that, the TCP connection carries framed
messages instead of request/response cycles.
Frames, heartbeats, and close codes
Each WebSocket message is a frame with an opcode (text, binary, ping, pong, close). Libraries usually handle fragmentation; application code sees whole messages. Because intermediaries (NATs, load balancers, mobile carriers) drop idle TCP sessions, production services send periodic ping/pong heartbeats or application-level keepalives. When shutting down gracefully, send a close frame with a status code (1000 = normal) so the peer does not mistake it for a network fault.
Always use wss:// (TLS) in production. Plain
ws:// is trivially intercepted and blocked on many networks. The TLS
certificate covers the same hostnames as your HTTPS site.
Server-sent events in practice
SSE reuses familiar HTTP semantics. The server sets:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Then it writes event lines the browser parses:
id: 42
event: price
data: {"symbol":"SOL","usd":142.50}
The trailing blank line delimits events. Clients use
new EventSource('/stream'); the browser handles reconnection and sends
Last-Event-ID so the server can replay missed events after a drop.
SSE limitations matter for architecture:
- Text only — binary payloads need base64 encoding or a different transport.
- One server → one client direction — client actions still use normal HTTP POST.
- Browser connection limits — roughly six parallel connections per host on HTTP/1.1; HTTP/2 multiplexing helps but some proxies still cap SSE streams.
- No custom headers in EventSource — auth often uses cookies (same-site) or query tokens; for header-based JWT, fetch + ReadableStream or WebSockets may be simpler.
For dashboards, build logs, and "payment detected" notifications after a Solana Pay transfer, SSE is often the sweet spot: server pushes confirmation once your backend sees the tx, while the user still polls payment verification as a fallback.
When WebSockets win
Choose WebSockets when:
- High-frequency client → server messages — game input, voice signaling, collaborative editing cursors.
- Binary protocols — compressed game state, protobuf, or custom framing without base64 tax.
- Multiplexed channels — one socket, many logical topics (though many teams still prefer one SSE stream per concern for simplicity).
- Sub-100 ms round trips — trading UIs, live auctions, rhythm games.
Blockchain RPC providers expose WebSocket endpoints for
accountSubscribe and logsSubscribe so backends react to
on-chain events without hammering HTTP
RPC rate limits.
Wallets still use HTTP for most reads; subscriptions are for services that must
detect a deposit or program log in near real time.
Multiplayer games add another layer: peer-to-peer or relayed UDP/TCP paths after matchmaking, as covered in our NAT traversal and game networking explainer. WebSockets handle lobby chat and metadata; the actual game loop often uses lighter UDP once NAT is punched.
Scaling and production pitfalls
Long-lived connections change how you deploy. A stateless REST API behind a round-robin load balancer "just works." A WebSocket farm does not — unless every server holds all state (rare) you need:
- Sticky sessions (session affinity) — route the same client to the same origin for the life of the socket.
- Pub/sub backplane — Redis, NATS, or Kafka so server A can push to a client connected to server B.
- Connection limits — each open socket consumes file descriptors and memory; capacity-plan separately from QPS.
- Graceful drain — on deploy, stop accepting new sockets, broadcast a reconnect hint, wait for natural churn before killing the process.
CDNs and reverse proxies differ widely. Some terminate WebSockets at the edge; others
require explicit proxy_read_timeout bumps. SSE through nginx needs
proxy_buffering off or events batch until the buffer fills — looks "broken"
for minutes. Test through the same path users hit, not just localhost.
Reconnect and idempotency
Mobile clients background tabs; laptops sleep; Wi-Fi roams. Assume every socket dies
without warning. Clients should exponential-backoff reconnect, resubscribe to topics,
and reconcile state with a snapshot HTTP call (missed messages since
lastSeenId). Server handlers must be idempotent — a
duplicate "place order" after reconnect should not double-charge.
Security checklist
- Authenticate during the HTTP upgrade (cookies, bearer token in Sec-WebSocket-Protocol, or short-lived ticket query param).
- Validate
Originon the server — prevents cross-site WebSocket hijacking from malicious pages. - Rate-limit connection attempts and messages per user; WebSockets bypass per-request WAF rules once open.
- Do not trust client-sent JSON without schema validation — same as REST.
- Log connection lifecycle without logging message bodies that contain PII or session tokens.
Real-time channels do not replace OAuth or wallet signatures for sensitive actions — they only deliver events faster. Authorization models from OAuth 2.0 and OIDC still apply when the stream carries user-specific data.
Decision matrix
| Pattern | Direction | Latency | Complexity | Best for |
|---|---|---|---|---|
| Short polling | Client pulls | Seconds | Low | Rare updates, internal admin tools |
| Long polling | Client pulls (held) | Sub-second | Medium | Sparse events, legacy infra |
| SSE | Server pushes | Sub-second | Medium | Feeds, notifications, live logs |
| WebSocket | Both ways | Milliseconds | Higher | Chat, games, trading, RPC subscriptions |
Measure before optimizing. A payment status page that refreshes every two seconds via polling may be indistinguishable from SSE to users — and far easier to operate. Reach for WebSockets when profiling proves HTTP overhead or latency is the bottleneck.
Related guides
- HTTP caching explained — when polling and REST still win
- Solana RPC endpoints explained — HTTP vs WebSocket on chain data
- NAT traversal and game networking relays
- Core Web Vitals explained — keep real-time scripts from hurting INP