Server-Sent Events (SSE) is a one-way streaming protocol over HTTP. The server pushes events to the client; the client reads them as they arrive. Compared to WebSockets, SSE is simpler and works through HTTP infrastructure — proxies, load balancers, CDNs — without special configuration.
For server-to-client streaming use cases, SSE is often the better choice than WebSockets. This page covers when and why.
SSE uses a long-lived HTTP response. The server writes events as they happen; the client (browser-native EventSource) parses them.
Server side:
GET /events
Accept: text/event-stream
Response (streaming, never closes):
data: {"order":"abc","status":"shipped"}
data: {"order":"def","status":"delivered"}
Client side:
const events = new EventSource('/events');
events.onmessage = (e) => {
const data = JSON.parse(e.data);
handle(data);
};
The connection stays open; the server writes new events; the client receives them in order.
SSE format is line-oriented:
event: order_update
data: {"id":"123","status":"shipped"}
id: 42
event: order_update
data: {"id":"124","status":"pending"}
id: 43
Fields:
event: optional event name for client-side dispatchdata: payload (one or more lines)id: optional event ID for resumptionretry: optional reconnection delay in millisecondsEmpty line separates events.
EventSource automatically reconnects on disconnect. The client sends the last received event ID:
GET /events
Accept: text/event-stream
Last-Event-ID: 42
Server resumes from event 43. The reconnection logic is built-in to the browser EventSource — no client code needed.
The server must support Last-Event-ID for resumption to work; otherwise reconnection misses events.
| Concern | SSE | WebSocket |
|---|---|---|
| Direction | Server → client only | Bidirectional |
| Protocol | HTTP | Custom upgrade from HTTP |
| Auto-reconnect | Built-in | Manual |
| Last-event resumption | Built-in | Custom |
| Browser support | EventSource native | WebSocket native |
| Proxy/CDN compatibility | Excellent (just HTTP) | Variable |
| Complexity | Low | Higher |
For one-way streaming (notifications, live updates, AI streaming responses), SSE is simpler. WebSockets pay off when the client also needs to send data through the same channel.
Push notifications from server to client. New messages, status changes, alerts.
Stock tickers, sports scores, dashboards. Periodic updates pushed as they happen.
Modern LLM APIs (OpenAI, Anthropic) use SSE for streaming responses. The server emits tokens as the model generates them.
Long-running operations (uploads, batch processing, builds). Server emits progress events; client renders.
Long idle connections may be closed by proxies. Send a periodic comment line:
:keep-alive
The empty colon-line is a comment; clients ignore it. Keeps the connection alive without affecting the data stream.
EventSource doesn't support custom headers. Two approaches:
GET /events?token=... — works but tokens in URLs are loggedFor APIs requiring header-based auth, custom EventSource implementations or fetch+ReadableStream alternatives exist.
SSE doesn't have native backpressure. If the client can't keep up, events queue or are dropped depending on infrastructure.
For streams where every event must be delivered, consider:
For multi-topic streams, the server filters per-client:
GET /events?topics=orders,shipments
Or use named events that clients can ignore:
events.addEventListener('orders', handleOrder);
events.addEventListener('shipments', handleShipment);
Last-Event-ID not handled; clients miss events on reconnect.