IBRAVRA Media Network
Website & Hosting • 12 min read

WebSocket vs Server-Sent Events: The Realtime Architecture Playbook

WebSocket, Server-Sent Events, and long polling are three different bills for the same promise: live data. This playbook runs the connection math from real vendor pricing, the reconnection semantics from the protocol documents, and the proxy constraints that kill each transport in production.

By Enow A. Jovial • Published 2026-11-13

WebSocket vs Server-Sent Events: The Realtime Architecture Playbook

A live dashboard, a chat room, and a price ticker all make the same promise: the screen updates without a refresh. Behind that promise are three different bills. WebSocket holds one connection open and talks both ways. Server-Sent Events holds one connection open and talks one way, with the browser doing the reconnect work. Long polling opens a new request every few seconds and asks again. Pick the wrong one and the cost is a connection ceiling at 2 a.m. or an invoice ten times the estimate. This playbook prices all three from vendor pages read on November 13, 2026.

The transport decision is a cost and failure-mode decision, not a speed decision. WebSocket wins when the client must send. SSE wins when the flow is one-way and the browser should own reconnection. Long polling wins when updates are rare. The rest is arithmetic that proves which case you are in.

What each transport actually is

A WebSocket, defined by RFC 6455, is a full-duplex protocol over a single TCP connection. It starts as an ordinary HTTP request and upgrades: the client sends GET with Upgrade: websocket, Connection: Upgrade, a Sec-WebSocket-Key, and Sec-WebSocket-Version: 13, and the server answers 101 Switching Protocols. RFC 6455 §1.3 designed the handshake so one port serves both HTTP and WebSocket clients. Once upgraded, either side can send at any time. The protocol also defines control frames — ping in §5.5.2, pong in §5.5.3, and a close handshake in §5.5.1. MDN records the browser API as baseline widely available since July 2015.

RFC 6455 handshake example with 101 Switching Protocols
RFC 6455 handshake example with 101 Switching Protocols

Server-Sent Events is the opposite shape: one-way, server to client, over plain HTTP. The browser opens an EventSource against a URL and the server streams text events down that response. MDN states the limit plainly: "This is a one-way connection, so you can't send events from a client to a server." Anything the client needs to send goes over a separate ordinary HTTP request.

Long polling is not a protocol at all. The client sends an HTTP request, the server holds it until an event arrives or a timeout fires, the client receives the answer and immediately asks again. Every cycle is a fresh request, and no state persists on the server between polls.

The decision table

Read the row that matches your traffic shape, then check the cost section.

QuestionWebSocketServer-Sent EventsLong polling
Client must send dataYes, nativeNo — separate HTTP requestYes, each poll is a request
Server pushes without a requestYesYesNo — only answers a poll
Browser reconnects on its ownNo, your code does itYes, with resumeYes, by design
Passes a plain HTTP proxyOnly if Upgrade passesYesYes
Message size limitNone in the protocolText lines, keep them smallBounded by the request
Best fitChat, collaboration, biddingTickers, feeds, status, alertsRare updates, hostile networks

The high-traffic web infrastructure Vercel vs Dokku on Hetzner playbook covers where these connections live once the transport is chosen.

The connection math

Realtime cost has two parts: holding connections open, and moving messages. The numbers below are derived arithmetic from prices read on November 13, 2026 — not measurements.

Connection Minutes=N×60×24×30\text{Connection Minutes} = N \times 60 \times 24 \times 30
Monthly Cost=Connection Minutes1,000,000×$0.25+Messages1,000,000×$1.00\text{Monthly Cost} = \frac{\text{Connection Minutes}}{1{,}000{,}000} \times \$0.25 + \frac{\text{Messages}}{1{,}000{,}000} \times \$1.00

The rates come from Amazon API Gateway's own worked example for WebSocket APIs in US East (N. Virginia): $1.00 per million messages and $0.25 per million connection minutes. The same page states the metering rules the formula depends on: messages up to 128 KB, metered in 32 KB increments so a 33 KB message counts as two, and ping and pong control frames are not metered and incur no charge. The free tier is 1 million messages and 750,000 connection minutes a month for up to 12 months — irrelevant at 10,000 connections.

Work it for 10,000 connections held open all month. Connection minutes are 10,000 × 60 × 24 × 30 = 432,000,000, which at $0.25 per million is $108.00 for connectivity alone. Now add traffic. One message a minute to each connection is 432 million messages, at $1.00 per million that is $432.00. Total: $540.00 a month, before the free tier. Cut the message rate to one client in ten and the message bill falls to $43.20 while the $108.00 connectivity floor does not move.

The same 10,000-connection question on Pusher Channels has a different shape, because Pusher sells concurrent connections in plan tiers rather than metering minutes. Read on November 13, 2026: Sandbox is free at 100 concurrent connections and 200,000 messages a day; Startup is $49 a month for 500 concurrent; Pro is $99 for 2,000; Business is $299 for 5,000; Premium is $499 for 10,000 concurrent connections and 20 million messages a day. So 10,000 concurrent connections on Pusher is exactly the Premium plan: $499 a month, or $0.0499 per concurrent connection per month, with the message allowance bundled. The step matters: 10,001 connections costs the next tier, $699 a month for 15,000. Price the tier boundary, not the average.

Ably bills by monthly active users, not concurrent connections. Free covers 500 MAUs at $0; Pro starts at $29 a month at 1,000 MAUs billed annually; Business starts at $349 a month, also at 1,000 MAUs, and carries the 99.999% uptime SLA. No per-connection price can be derived without a MAU-to-concurrent ratio from your own traffic. Ably's own stated in-region message delivery latency is 6.5ms — a vendor claim, not an independent measurement.

Long polling inverts the arithmetic. There is no connection floor, because nothing is held open — but every poll is a request, and requests are counted. One poll every five seconds from 10,000 clients is 10,000 × 17,280 = 172,800,000 requests a day. At a 30-second interval the same audience makes 28,800,000 requests a day. Whether it beats a held connection is a question of update frequency: poll rarely and long polling is nearly free; poll often and it is the most expensive transport in this playbook. Run the numbers for your own traffic in the pricing calculator before committing to an interval.

Reconnection and resume: where SSE earns its keep

A connection that never drops needs no reconnection logic. Production connections drop constantly — deploys, load balancer restarts, mobile networks switching cells, laptops closing. The difference is measured in code you must write.

SSE handles it in the browser. When the connection is lost, EventSource reconnects on its own, and the stream format gives the server two controls: the retry field, in milliseconds, sets how long the browser waits before reconnecting, and the id field sets the last event ID, which the client sends back on reconnect so the server can resume from the event the client missed. That is a complete resume protocol with zero client code.

WebSocket has no equivalent. RFC 6455 §5.5.1 defines a close handshake, not a resume: when the connection ends, it ends, and your code must reconnect, re-authenticate, re-subscribe to the channels it was on, and ask the server for everything it missed — with a backoff, so that a deploy which drops all connections at once does not bring them all back in the same second. Every production WebSocket client needs that state machine.

The WebSocket API has no backpressure. MDN states that when messages arrive faster than the application can process them, the application will either fill up the device's memory by buffering those messages, become unresponsive from 100% CPU usage, or both. A fast producer and a slow consumer is a memory leak with a network cause — throttle at the server, drop or coalesce updates, or use the WebSocketStream API, which provides backpressure automatically.

Long polling reconnects by definition: each cycle is a new request, so a dropped poll is just a poll that returns early. The resume question becomes a data question instead of a connection question — send a cursor or a since timestamp with each poll and the server answers from there. Simple, and the reason long polling survives in hostile networks.

Proxies, load balancers, and the middle of the network

The transport you choose must survive the network between your server and your user, and that network is not yours. A WebSocket begins as an HTTP Upgrade request, and every proxy, load balancer, and firewall on the path must understand the Upgrade handshake, pass it through, and then hold the connection open indefinitely. Most modern infrastructure does this, but corporate proxies and older load balancers still fail it, silently, for users who never file a bug report. SSE and long polling ride plain HTTP requests, so they pass through the same infrastructure that already serves the site.

The load balancer shape differs too. A WebSocket connection is sticky by nature: once it lands on a server, it stays there, so the balancer must keep it there and the server must hold its state, or the application must share state through a message broker so any server can publish to any connection. An SSE connection is sticky the same way while open, but a reconnect can land on a different server without the client noticing — provided the new server can resume from the event ID. Long polling needs no stickiness at all: each request can land anywhere, which is why it scales on the most ordinary HTTP infrastructure. The Cloudflare Workers D1 Hyperdrive edge SQL latency benchmark prices the publish side: the database read behind each event is usually the slower half.

Who this is for — and who should avoid each transport

This playbook is written for the operator of a SaaS product that needs live data in the browser: a B2B dashboard, a collaboration tool, a trading or booking interface, an API that pushes status to its customers. Whether the company is an LLC, a C-Corp, or an S-Corp, the decision is the same shape — an EIN on the vendor account, a CPA who can read the invoice, books kept on a GAAP basis so the realtime bill is visible as a line item instead of a surprise, and a contract the founder actually read.

Avoid WebSocket when the flow is one-way. You are paying for a full-duplex channel and writing a reconnection state machine to get what SSE gives you free. Avoid SSE when the client must send anything beyond a subscription — a chat box, a cursor position, a bid — because bolting a second HTTP channel onto SSE recreates the complexity you chose SSE to avoid. Avoid long polling when updates arrive more often than about once every thirty seconds. And avoid all three when the data does not actually change live: a page that updates twice an hour is a page that should be cached, and the CDN stale-while-revalidate edge purge cache architecture is the cheaper answer.

The choose-and-ship protocol

Run this in order. Each step produces a number or a decision that the next step depends on, and skipping one is how teams end up re-platforming six months in.

  1. Measure the traffic shape first: peak concurrent users, messages per user per minute, and the share of messages that flow client-to-server. Write the three numbers down; every later step consumes them.
  2. Run the cost formula twice, once on API Gateway rates and once for the Pusher tier covering peak concurrency, and budget the larger. The tier boundary is the number on the invoice.
  3. Decide the direction question with one sentence: if the client ever sends, it is WebSocket; if the flow is strictly one-way, it is SSE; if updates are rarer than one every thirty seconds, price long polling before deciding.
  4. Test through the worst network your users have — a corporate proxy, a mobile handover, a VPN — and record which transports survive.
  5. Build the reconnection path before the happy path: SSE resume from the event ID, or a WebSocket state machine with backoff, jitter, re-authentication, and re-subscription. Kill the server mid-session once and watch what the client does.
  6. Set the server-side limits in writing: maximum connections per server, maximum message rate per connection, and the behaviour at the limit — queue, drop, or disconnect.
  7. Put the realtime bill on its own line in the monthly accounts, with an alert at eighty percent of the tier or the budgeted message count.

Execution checklist

  • [x] Traffic shape measured: peak concurrency, message rate, client-to-server share
  • [x] Cost formula run for API Gateway rates and the Pusher tier at peak concurrency
  • [x] Direction decided in one sentence: two-way, one-way, or rare updates
  • [x] Worst-network test passed: corporate proxy, mobile handover, VPN
  • [x] Reconnection path built and tested with the server killed
  • [x] Server limits written down: connections per server, rate per connection, behaviour at the limit
  • [x] Billing alert set at eighty percent of the tier
  • [x] Backpressure handled: throttle, coalesce, or WebSocketStream — never an unbounded buffer
Price the tier boundary, not the average. Pusher Premium covers exactly 10,000 concurrent connections at $499 a month; the 10,001st connection moves the bill to $699. Set the alert at eighty percent of the tier and treat the boundary as a capacity event, not a billing surprise.

Founder & CEO Insight — Enow A. Jovial

"Every realtime product I have seen fail failed the same way: the demo worked, the launch worked, and then ten thousand real users arrived at once and the server stopped accepting connections. The transport was never the problem. The problem was that nobody had written down the connection number before the launch."
— Enow A. Jovial, Founder & Chief Executive Officer

The decision in advance

WebSocket is the default for two-way realtime, SSE for one-way realtime, long polling when updates are rare or the network is hostile. Three numbers decide it — peak concurrency, messages per user per minute, client-to-server share — run through the cost formula and tested on the worst network the users have. Write them down before the first line of transport code.