v10.2 Fix layout switching, pane, and high-DPI display issues See what's new

How to Debug SSE / EventSource Streams (Step by Step)

Inspect Server-Sent Events in real time

Server-Sent Events (SSE) is a one-way streaming protocol where the server holds a single HTTP response open and pushes a sequence of text events to the client, which reads them through the browser's EventSource API. To debug SSE / EventSource streams you watch three things: the response headers that put the connection into streaming mode, the individual events as they arrive, and how the stream finally ends. The workflow below covers the on-the-wire format, the proxy and encoding problems that silently break streaming, and how to read a live stream in practice.

What to inspect when you debug an SSE / EventSource stream

A complete SSE debugging pass looks at four layers, in order:

  1. The response headers - Content-Type: text/event-stream plus Cache-Control: no-cache. If the content type is wrong, the browser never treats the body as an event stream and no events fire.
  2. The events - the data, event, id, and retry fields the server actually writes, plus the blank line that dispatches each event.
  3. Reconnection state - the last id the client saw and the retry interval, which decide what happens after a drop.
  4. How the stream ends - whether the server closed cleanly or the connection was cut mid-event, which is often the single most useful clue.

How Server-Sent Events work on the wire

Most streaming bugs are easier to read once you know what the bytes mean. SSE is defined by the WHATWG HTML specification, and the client API is documented on MDN.

One long-lived HTTP response

An SSE stream starts as an ordinary GET request with Accept: text/event-stream. The server replies 200 OK with Content-Type: text/event-stream and then never closes the body - it keeps writing into the same response for as long as the connection lasts. Over HTTP/1.1 the body is delivered with chunked transfer encoding; over HTTP/2 it is a stream that stays open. The payload must be sent uncompressed: SSE is a live text stream, so an identity encoding is the norm and a gzip/Brotli layer is what breaks incremental delivery.

The event stream format

The body is UTF-8 text parsed line by line. Each non-empty line is a field: value pair, a leading : marks a comment (often a keepalive), and a single optional space after the colon is stripped. Four fields are defined:

Field Purpose
dataThe payload; repeat it to build a multi-line value joined with \n
eventNames the event so the client can listen for a custom type; defaults to message
idSets the event ID the client remembers for reconnection
retryReconnection delay in milliseconds (digits only)

The detail that trips people up is dispatch: an event is only delivered when the parser hits a blank line. A block with data lines but no trailing empty line sits in the buffer and the client sees nothing. A leading UTF-8 byte order mark is stripped once, and unknown fields are ignored rather than rejected.

GET /events HTTP/1.1
Accept: text/event-stream

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

: keepalive comment - ignored by the client

event: message
data: {"text":"Review Summary"}

id: 42
event: progress
data: {"done":30}
data: {"total":100}

retry: 3000

Reconnection: id, Last-Event-ID, and retry

Reconnection is built into the protocol, which is the main reason teams pick SSE over a raw socket. Whenever the server sends an id, the client stores it; if the connection drops, the browser reconnects automatically and replays the stored value in a Last-Event-ID request header so the server can resume from where it left off. The retry field overrides the default reconnection delay. The catch worth debugging: replay only works if the server actually honors Last-Event-ID - many deployments send IDs but never resume, so events are silently lost across a reconnect.

SSE vs WebSocket

SSE is server-to-client only and rides plain HTTP, so it passes through proxies, CDNs, and load balancers without a protocol upgrade and brings automatic reconnection for free. WebSocket is full-duplex but needs an upgrade handshake and its own reconnection logic. For live dashboards, notifications, log tails, and token-by-token AI/LLM responses - anywhere data flows one way - SSE is simpler and easier to debug.

Why SSE streams are hard to debug

Most "my EventSource never fires" reports are not application bugs - they are something between the server and the client buffering or rewriting the stream. The usual suspects:

  • Proxy buffering. Nginx buffers proxied responses by default, so events pile up in the buffer and the client sees nothing for minutes; you need X-Accel-Buffering: no or proxy_buffering off. CDNs such as Cloudflare buffer too unless the route streams.
  • A missing blank line. Forgetting the second newline after an event is the most common SSE bug - the event never dispatches and the stream looks dead.
  • The wrong Content-Type. Anything other than text/event-stream in the response HTTP headers means the browser does not parse the body as events.
  • Compression in the path. A gzip or Brotli layer added by the server or a proxy buffers the stream to compress it and defeats incremental delivery.
  • CORS. A cross-origin stream needs the right Access-Control-Allow-Origin; the connection fails before the first event with only a generic error event on the client.
  • The HTTP/1.1 connection cap. Browsers allow about six connections per origin; several open SSE streams plus normal requests can exhaust the pool. HTTP/2 multiplexing removes the limit.

The hard part is that the browser's error event is opaque - it tells you the connection failed, not why. You need to see the raw stream on the wire to tell a buffering proxy from a missing newline from a truncated response.

Ways to debug an SSE stream

Method Good for Limitation
Browser DevTools (EventStream tab) A page you load yourself in the browser Browser tabs only; Chrome's view is bare and shows nothing for non-browser clients
curl -N Confirming the raw bytes and headers from the shell Manual, no per-event view, no decryption of another app's HTTPS
System-wide capture (HTTP Debugger) Any process, including HTTPS, localhost, and HTTP/2 Windows only; inspects rather than modifies the stream

From the shell, the fastest first check is to read the stream directly:

curl -N -H "Accept: text/event-stream" https://api.example.com/events

The -N flag disables curl's own output buffering, so events print as they arrive. If they appear here but not in your app, the problem is on the client; if they stall here too, it is the server or something buffering in between.

How to debug an SSE stream step by step

  1. Confirm the response headers. Verify the response is 200 with Content-Type: text/event-stream and no compression. A wrong content type or a Content-Encoding: gzip header explains a stream that never starts.
  2. Watch events arrive in real time. Follow events on a timeline as they stream in. If the headers look right but no events appear, suspect proxy buffering or a missing blank line between events.
  3. Read each event's fields and payload. Check the event type, id, and the data payload - pretty-printed when it is JSON - so you compare application data, not a wall of raw text.
  4. Check reconnection and how the stream ends. Confirm the id/Last-Event-ID and retry behavior across a reconnect, and check whether the stream closed cleanly or was cut off mid-event.

In the browser, Firefox and Chrome DevTools expose received events under the Network tab when you select the stream request. That works for pages you load yourself. It does not help when the stream is consumed by a desktop app, a background service, a CLI tool, a mobile emulator, or one backend calling another - and it cannot show a stream that opened before DevTools was attached.

Debugging SSE events in HTTP Debugger

HTTP Debugger Pro captures Server-Sent Events system-wide on Windows without a proxy or browser extension, so it sees streams from any process - browsers, desktop apps, services, CLI tools, and AI agents - including encrypted HTTPS, localhost, and HTTP/2. Because capture is not tied to a browser tab, it also catches a stream that was already open before you started looking.

A response with Content-Type: text/event-stream is marked as an SSE stream in the main grid, and each connection gets its own Server-Sent Events view: a timeline of events as they arrive, with a sequence number, direction, time offset, event type, byte size, and a payload preview. You can filter by event type or direction and search across payloads.

Server-Sent Events view in HTTP Debugger showing streamed message events with offset, byte size, and a decoded JSON payload

For each event, the Text tab shows the data payload as readable text and auto-formats it when it is JSON, with Raw and Hex views alongside. The Raw view reconstructs the id, event, and data lines exactly as they came off the wire, so you can confirm the field structure the server actually sent.

Per-event SSE info in HTTP Debugger: direction, event type, time offset, and byte size

The Info tab breaks out each event's direction, type, time offset, and byte size, and the stream's summary records whether it ended cleanly or was truncated - the signal that separates a normal close from a connection that was cut mid-event. SSE traffic is read-only here: HTTP Debugger inspects and decodes events rather than injecting or rewriting them.

It complements browser tooling: use DevTools for a page you control, and a system-wide HTTP sniffer when the stream lives outside the browser or rides encrypted HTTP traffic you cannot attach DevTools to. The same connection's response headers and events sit next to the rest of your captured session, which makes it a practical HTTP analyzer for real-time protocols like WebSocket and gRPC.

Common mistakes when debugging SSE

  • Blaming the client for a buffering proxy. If the events stall on the wire too, the client is fine - look at Nginx, a CDN, or any hop that buffers the response.
  • Forgetting the blank line. An event without its trailing empty line never dispatches; the stream looks alive but the client gets nothing.
  • Compressing the stream. A gzip or Brotli layer turns a live stream into a buffered one and stops incremental delivery.
  • Trusting Last-Event-ID without server replay. Sending id values does nothing unless the server resumes from the Last-Event-ID header on reconnect.
  • Debugging only in the browser. DevTools cannot see SSE from desktop apps, services, backend-to-backend calls, or a stream opened before it attached.

FAQ

How do I view the events in an SSE stream?

Open the stream request in your browser's Network tab to read received events, or use curl -N -H "Accept: text/event-stream" URL from the shell to print them as they arrive. For a stream from a non-browser process, use a system-wide capture tool that lists each event with its type, size, and payload.

Why is my SSE connection hanging with no events?

The connection opens but no events arrive almost always means buffering or framing. Check that a proxy is not buffering the response (disable Nginx proxy_buffering or set X-Accel-Buffering: no), that each event ends with a blank line, and that nothing in the path compresses the stream.

What is the difference between SSE and WebSocket?

SSE is one-way, server-to-client over plain HTTP with automatic reconnection built in. WebSocket is full-duplex but needs an upgrade handshake and its own reconnection logic. Use SSE when data flows in one direction; use WebSocket when both sides need to send.

Can I debug SSE over HTTPS or from a non-browser app?

Yes. Browser DevTools only sees streams from pages it is attached to. For desktop apps, services, CLI tools, or backend-to-backend calls, use a system-wide capture tool such as HTTP Debugger Pro that decrypts HTTPS with a local root certificate and shows events from any process on the machine.

Does SSE work over HTTP/2?

Yes. SSE is plain HTTP, so it runs over HTTP/2, where stream multiplexing removes the roughly six-connection-per-origin limit that HTTP/1.1 imposes on multiple open streams.

HTTP Debugger Pro

Windows 11 / 10

A proxy-less HTTP/HTTPS sniffer for Windows that captures traffic system-wide from any process.

Download free 7-day trial

No registration required · Trusted since 2007