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."

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:

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:

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:

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

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