WebSocket vs SSE (Server-Sent Events)
WebSockets and server-sent events (SSE) both let a server push data to a client without the client polling for it. The difference is direction. A WebSocket is a full-duplex channel where either side can send at any moment, while SSE is a one-way stream from server to browser carried over an ordinary HTTP response. If only the server has something to say, SSE is the simpler choice.
How each protocol works
A WebSocket begins as an HTTP request asking the server to upgrade the connection. Once the server agrees, the connection switches to the WebSocket protocol (ws://, or wss:// over TLS) and both sides exchange framed messages over the same TCP connection until one of them closes it. After the handshake, nothing about the traffic looks like HTTP anymore.
SSE never leaves HTTP. The client opens a normal GET request, the server responds with Content-Type: text/event-stream and holds the response open, writing data: lines as events happen. In the browser the entire client side is the built-in EventSource object, which reconnects on its own and tells the server which event ID it last received so the stream can resume.
Comparing the tradeoffs
| Aspect | WebSocket | SSE |
|---|---|---|
| Direction | Both ways | Server to client only |
| Transport | WebSocket protocol after an HTTP upgrade | Plain HTTP |
| Payloads | Text and binary | UTF-8 text only |
| Reconnection | You implement it | Built into EventSource |
| Proxies and firewalls | Often need explicit configuration | Passes as ordinary HTTP |
| Connection limit | Not subject to the per-domain cap | Six per domain on HTTP/1.1 |
The connection limit is the detail that catches people out. Under HTTP/1.1 a browser allows six connections per domain, and an open SSE stream holds one of them for the life of the page, so a handful of tabs can starve the rest of your requests. Serving the stream over HTTP/2 raises that ceiling high enough that it stops being a concern.
Does SSE work over HTTP?
Yes, over every version of it. SSE needs no protocol upgrade, so it runs over HTTP/1.1, HTTP/2, HTTP/3, and HTTPS, and anything that can serve an HTTP response can carry it. When a stream fails in production, the cause is almost always infrastructure that treats a long response as a short one: a reverse proxy buffering the body (in nginx, proxy_buffering off or an X-Accel-Buffering: no response header), a load balancer closing a connection it thinks is idle (send a : comment line every 15 to 30 seconds), or gzip holding events until its window fills. One HTTPS rule to remember: an EventSource opened from an HTTPS page to an http:// URL is blocked as mixed content.
Which one is faster
Neither is meaningfully faster at delivering a single event. Both hold a connection open, so an update reaches the client in one network trip with no polling interval to wait out. The differences show up in overhead per message and in what the connection costs the server. A WebSocket frame adds 2 to 14 bytes of framing, while SSE wraps each event in a data: line and a blank line, and because the stream is UTF-8 only, binary has to be base64-encoded, inflating it by about a third. Against that, an SSE stream is an ordinary HTTP response, so it multiplexes over HTTP/2 and reuses the compression, logging, and load balancing you already run, while a WebSocket needs upgrade support and sticky routing at every hop. At a few messages per second the choice makes no measurable difference; at thousands of small binary messages per second, the socket wins.
When SSE is the right choice
SSE suits anything shaped like a feed: live scores, deploy logs, price tickers, notification counts, and the token-by-token output of an LLM, which is why ChatGPT streams over SSE rather than a socket. Because the stream is just an HTTP response, it inherits the authentication cookies, compression, load balancers, and request logging you already have, and it degrades to a reconnect rather than a broken protocol when a proxy interferes. If your server runs Node, building an SSE endpoint in Express covers the parts that trip up a first deployment: replaying missed events, heartbeats, and proxy buffering.
When you need a WebSocket
Reach for a WebSocket when the client sends nearly as much as it receives: chat, multiplayer game state, collaborative editing, remote terminals, and live cursors. Sending upstream over SSE means firing separate HTTP requests alongside the stream, and once you are doing that on every keystroke the socket is both faster and easier to reason about. WebSockets also carry binary frames, so audio, video, and packed formats belong there. The cost is that you own reconnection, heartbeats, and backpressure yourself, and that sticky connections make horizontal scaling harder than stateless HTTP.
Choosing between WebSockets and SSE
A quick test: write down what the client needs to send after the connection opens. If the answer is “nothing”, use SSE and keep the whole thing inside HTTP. If the answer is a steady stream of messages, use a WebSocket and accept the extra operational work. For the wider set of options, compare long polling vs WebSockets and WebSocket vs REST API. What is replacing WebSockets? looks at where SSE and WebTransport have taken over jobs that used to default to a socket. And if you have read that SSE is on its way out, is SSE deprecated? explains which nearby features were actually retired.
Both protocols assume a client that stays connected and waits, which is right for a browser and wrong for a backend. When one server needs to tell another that something happened, holding a socket open is wasteful, and webhooks are the usual answer: a short HTTP request sent only when there is an event to report. We break that comparison down in webhooks vs server-sent events. If you are the one sending those events to customers, Svix handles the retries, signatures, and delivery logs so you do not have to build them.