Server-sent events (SSE) are a web standard that lets a server push a stream of text messages to the browser over a single, long-lived HTTP response. Among web APIs, SSE is the simplest way to deliver real-time updates in one direction, from server to client, using the browser’s built-in EventSource interface and the text/event-stream media type.
SSE is specified in the HTML Living Standard and works with ordinary HTTP infrastructure. Unlike WebSocket, it does not switch protocols, and unlike polling, it does not repeat requests: the connection stays open and the server writes new events whenever it has something to say, while the browser handles automatic reconnection and event replay on its own.
How server-sent events work
The client opens a stream with new EventSource(url), which sends a normal GET request with the header Accept: text/event-stream. The server answers with status 200 and the content type text/event-stream, then keeps the response open and writes events as UTF-8 text. Each event consists of one or more field lines and ends with a blank line.
The browser parses the stream incrementally and dispatches a DOM event for every completed message. Unnamed messages fire the message event; messages with an event field fire a custom event type that the page subscribes to with addEventListener. If the connection drops, the browser waits and reconnects automatically.
The wire format defines only a handful of fields, summarized below.
| Field | Purpose | Notes |
|---|---|---|
| data | Payload of the message | Several data lines are joined with newlines |
| event | Names the event type | Defaults to message when omitted |
| id | Sets the last event ID | Sent back in the Last-Event-ID header on reconnect |
| retry | Reconnection delay in milliseconds | Browsers default to a few seconds |
| Comment line | Line starting with a colon | Ignored by the parser, useful as a heartbeat |
Reconnection, event IDs and delivery guarantees
The built-in reconnection mechanism is the main practical advantage of SSE. When the stream ends or the network fails, EventSource reopens the connection after the retry delay and sends the Last-Event-ID header containing the most recent id it received. A server that stores recent events can resume exactly where the client left off.
This gives at-least-once delivery only if the server implements replay. The protocol itself keeps no buffer; if events are not stored by ID, anything published during the outage is lost. A server can stop reconnection permanently by responding with HTTP 204 No Content or with a non-stream content type.
Idle connections are a frequent operational issue. Proxies and load balancers often close responses that stay silent for 30 to 120 seconds, so servers usually send a comment line as a heartbeat every 15 to 30 seconds and disable response buffering for the stream.
Server-sent events compared with other real-time techniques
SSE fills the space between simple polling and a full bidirectional WebSocket channel. The right choice depends on message direction, data format and the infrastructure in between.
| Technique | Direction | Data format | Main trade-off |
|---|---|---|---|
| Server-sent events | Server to client | UTF-8 text | Simple and HTTP-native, but one-way |
| WebSocket | Both directions | Text or binary | Full duplex, but needs its own protocol handling |
| Long polling | Server to client | Any | Works everywhere, but wastes requests |
| Web push | Server to device | Encrypted payload | Reaches closed tabs, but needs a push service |
| WebRTC data channel | Peer to peer | Text or binary | Low latency, but complex signaling |
Client-to-server messages in an SSE design simply use ordinary fetch or form requests. For many dashboards, notifications and feeds this split is cleaner than a WebSocket, because every request remains a normal HTTP request that existing authentication, logging and caching layers already understand.
Connection limits and HTTP versions
Each open stream occupies a connection, which matters under HTTP/1.1, where browsers allow about six connections per origin. Several tabs with streams can exhaust that budget and block other requests to the same host. Under HTTP/2 and HTTP/3, streams are multiplexed and the practical limit rises to the negotiated number of concurrent streams, typically 100 or more.
| Transport | Concurrent streams | Practical consequence |
|---|---|---|
| HTTP/1.1 | About 6 per origin | Share one stream across tabs or use a separate subdomain |
| HTTP/2 | Usually 100 or more | Many streams per tab are feasible |
| HTTP/3 | Negotiated, usually 100 or more | No head-of-line blocking between streams |
On the server, every client holds an open response, so event-driven runtimes that do not dedicate a thread per connection scale far better. Thread-per-request servers can run out of workers long before CPU or memory become the limit.
Practical choices and common pitfalls
Most SSE failures come from intermediaries and missing replay logic, not from the API itself. The following table maps typical symptoms to their causes.
| Symptom | Likely cause | Fix |
|---|---|---|
| Events arrive in bursts | Proxy or compression buffering | Disable buffering and flush after each event |
| Stream closes every minute | Idle timeout on a proxy | Send heartbeat comments |
| Missed messages after reconnect | No storage by event ID | Keep a replay buffer keyed by id |
| Other requests hang | HTTP/1.1 connection limit | Move to HTTP/2 or share one stream |
| Cannot send auth header | EventSource has no custom headers | Use cookies or a fetch-based stream reader |
EventSource supports cookies through withCredentials but not custom request headers, so token-based APIs often read the stream with fetch and a manual parser instead. Binary payloads must be encoded, for example as Base64, which adds roughly one third to the size.
The short version
Server-sent events work like a radio broadcast for a web page. The page tunes in once, and the server keeps talking whenever there is news, without the page having to ask again and again.
If the signal drops, the page tunes back in by itself and can tell the server which message it heard last. The page listens, the server speaks, and when the page needs to say something back, it uses an ordinary request.
Where it makes a difference
An online auction platform shows live bids on each item page. Thousands of visitors watch the final minutes of popular auctions, but only a few of them place bids. The team uses one SSE stream per page for price updates and normal POST requests for bids, served over HTTP/2.
Every event carries an incrementing id, and the server keeps the last five minutes of events in memory. When a visitor on a train loses signal for twenty seconds, the browser reconnects with Last-Event-ID and receives the missed bids at once. Viewers see accurate prices within a fraction of a second, and the existing HTTP load balancers and logs handle the traffic without a separate WebSocket gateway.