What are server-sent events (SSE)?
Server-sent events (SSE) are a browser standard for streaming updates from a server to a client over a single, long-lived HTTP response. The client opens a normal GET request, the server replies with Content-Type: text/event-stream and keeps the response open, writing one message at a time as things happen. Traffic flows in one direction only: server to client.
How the event stream works
The format is deliberately plain text. Each message is a block of lines ending in a blank line, and the only required field is data:
data: {"status":"deploying"}
event: finished
id: 42
data: {"status":"live","duration":31}
Four field names are defined. data carries the payload, event names the message type so the client can attach a specific listener, id labels the message for resumption, and retry tells the client how many milliseconds to wait before reconnecting. Anything else is ignored, and a line beginning with a colon is a comment, which servers often send periodically as a keepalive to stop an idle proxy from closing the connection.
On the browser side there is no library to install. The EventSource object handles the whole protocol:
const stream = new EventSource("/deployments/17/events");
stream.onmessage = (e) => console.log(JSON.parse(e.data));
stream.addEventListener("finished", (e) => stream.close());
Reconnection and resuming a stream
Automatic reconnection is the feature that makes SSE worth using over a hand-rolled streaming response. When the connection drops, EventSource waits and reconnects on its own. If the server sent an id with earlier messages, the browser includes the most recent one in a Last-Event-ID request header on the retry, so your handler can look up where the client left off and replay only what it missed. Getting that right requires keeping a short buffer of recent events server-side, but it turns a dropped connection into a gap of a few hundred milliseconds instead of lost data.
What SSE cannot do
The payload is UTF-8 text, so binary data has to be encoded, which costs you roughly a third in size. There is no upstream channel, so anything the client needs to send goes out as a separate HTTP request. And under HTTP/1.1 browsers cap connections at six per domain, and an open stream holds one of them for the life of the page, so a user with several tabs open can starve their own ordinary requests. Serving the stream over HTTP/2 or HTTP/3 raises that ceiling far enough that it stops mattering. Reverse proxies are the other common trap: buffering has to be off, or your messages arrive in batches when the buffer fills rather than when you write them.
When to use SSE
SSE fits anything shaped like a feed that the reader only watches: build logs, order status, live scores, notification badges, and token-by-token model output, which is why ChatGPT streams over SSE. Because it is ordinary HTTP, it inherits the cookies, authentication, compression, and request logging you already run. When the client also needs to talk back constantly, a socket is the better tool; we lay out that decision in WebSocket vs SSE, and long polling vs WebSockets covers the older approach SSE replaced.
One thing SSE is not is a way to notify another company's backend. It assumes a client sitting there with a connection open, which is fine for a browser and wrong for a server. In an event-driven architecture that job goes to webhooks, a short HTTP request sent only when there is something to report. See webhooks vs server-sent events for the full comparison. If you are the one sending those events out, Svix handles the retries, signatures, and delivery logs.