v10.3 Faster high-throughput capture and Windows compatibility fixes See what's new

How to Debug SSE Streams with HTTP Debugger

Inspect Server-Sent Events over HTTP/1.1 and HTTP/2 — headers, event payloads, IDs, and timing

  1. Request GET · text/event-stream
  2. Stream open HTTP response
  3. Events data · id · retry
  4. Close clean · truncated

Server-Sent Events (SSE) is a one-way streaming protocol: 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. Data flows from server to client only, over ordinary HTTP.

Unlike WebSocket, which opens a full-duplex channel through its own upgrade handshake, SSE needs no protocol upgrade and reconnects automatically, so it travels through proxies and CDNs as a plain HTTP response. AI assistants lean on it for token, tool-progress, and status streams, where data only ever flows one way.

To debug an SSE stream you cannot just read a URL and a response body the way you do with REST — the response never ends, and the useful signal is spread across three layers: the headers that put the connection into streaming mode, the individual events as they arrive, and how the stream finally closes.

How to debug SSE streams

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. In AI-agent streams, event names often separate tokens, tools, and status updates.
  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 keeps the body open - 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. For reliable incremental delivery, check whether the response is sent with Content-Encoding: identity or no content-encoding header. A gzip or Brotli layer in the server, proxy, or CDN path can buffer output and delay events that should arrive one at a time.

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 uses ordinary HTTP, so it passes through proxies, CDNs, and load balancers without a protocol upgrade and uses the browser API's built-in automatic reconnection. WebSocket is full-duplex but needs an upgrade handshake and its own reconnection logic. For live dashboards, notifications, log tails, and AI assistant 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 usually caused by 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. Some CDN and cache configurations can buffer responses too, so verify that the route really streams rather than assuming every hop passes chunks through immediately.
  • 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 can buffer the stream while compressing it and delay 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 that HTTP/1.1 browser connection cap, though deployments can still enforce their own concurrent stream limits.

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 distinguish a buffering proxy, a missing newline, and 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 instead of scanning raw stream 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.

Debug SSE streams with HTTP Debugger

HTTP Debugger captures Server-Sent Events streams and keeps them visible in the same grid as the rest of the session. Each captured stream shows its method, URL, status, type, size, and timing, and a dedicated Server-Sent Events pane breaks it down event by event.

  1. Start the SSE request from the client application or browser. HTTP Debugger captures it as a grid row showing method, URL, status, type, size, and timing, and a text/event-stream response marks it as an SSE stream with an event-count badge. If the grid is busy, click All Types and select SSE under Streaming to filter to event streams.
    HTTP Debugger main grid with a captured response marked as a Server-Sent Events stream HTTP Debugger type filter dropdown with DATA, GENERAL, and STREAMING groups, including an SSE entry
  2. Click the event-count badge in the SSE row. HTTP Debugger opens the Server-Sent Events pane on the right: a timeline of events for the selected stream with a sequence number, direction, time offset, event type, byte size, and a payload preview. You can filter the events and search across payloads. Selecting an event shows its detail in the Info, Text, Raw, and Hex tabs — Text renders the data payload and auto-formats it when it is JSON, while Raw reconstructs the id, event, and data lines exactly as they came off the wire, so you can confirm the field structure the server sent.
    Per-event SSE Info tab in HTTP Debugger showing direction, event type, time offset, and byte size Server-Sent Events pane in HTTP Debugger showing streamed events with offset, byte size, and a decoded JSON payload
  3. The Info tab is the first of the per-event tabs. For an SSE stream it groups the selected event's metadata under a single Event section:

    • Direction — always Recv; an SSE stream is server-to-client only.
    • Type — the event kind shown in the timeline: the custom event name, or Message when the server set none.
    • Offset — the event's time offset within the captured session, when timing is available.
    • Bytes — the captured data payload size for that event.
    • Event and ID — the raw event and id field values, listed only when the server sent them.

    The stream's summary also records whether it ended cleanly or was truncated — the signal that separates a normal close from a connection cut mid-event.

Common SSE debugging scenarios

"EventSource fires no events"

The connection opens but no message events arrive. Check the response first: anything other than Content-Type: text/event-stream means the browser never parses the body as events, and a Content-Encoding: gzip layer can buffer the whole stream before the first event escapes. If the headers look right, confirm each event ends with a blank line — a block of data lines with no trailing newline never dispatches.

"Events arrive in a burst instead of streaming"

Events show up, but all at once after a delay rather than one at a time. That is buffering between the server and the client, not an application bug. Run curl -N against the endpoint: if the burst happens there too, the cause is upstream — Nginx proxy_buffering, a CDN, or a compression layer holding the bytes — so set X-Accel-Buffering: no or disable buffering on the hop that stalls.

"The stream dies after about a minute of silence"

A stream that drops on a fixed interval is usually hitting a proxy or load-balancer idle timeout. SSE has no built-in ping, so a quiet stream looks idle to every hop in the path. Send a periodic comment line (: keepalive) so the connection keeps producing bytes, and compare the timeout against the gap before the drop.

"It reconnects, but events are lost"

The browser reconnects automatically and replays the last id in a Last-Event-ID request header, but replay only works if the server honors it. Many deployments emit id values and then ignore the header on reconnect, so the events produced during the drop are never resent. Confirm the server actually resumes from Last-Event-ID rather than restarting the stream.

"DevTools shows nothing, but the stream works"

Browser DevTools only sees streams from the page it is attached to. A stream consumed by a desktop app, a background service, a CLI tool, a mobile emulator, or one backend calling another never appears there, and neither does a stream that opened before DevTools was attached. Use a system-wide capture that reads the events from any process on the machine.

Limitations

System-wide capture shows exactly what crossed the wire, but it does not rewrite it. SSE traffic in HTTP Debugger is read-only: it inspects and decodes events rather than injecting or modifying them, and the Auto-Reply and HTTP Modifier rules apply to regular HTTP/HTTPS traffic, not event streams. Capture is Windows-only and reads the stream at the protocol layer, not your application's event schema. It complements browser tooling: use DevTools for a page you control, and the system-wide HTTP sniffer when the stream lives outside the browser or rides encrypted HTTP traffic you cannot attach DevTools to. For everything around the events — headers, timing, sizes, and the raw HTTP protocol exchange — the HTTP analyzer covers the rest. The same workflow applies to the rest of the stack — see the guides on debugging the HTTP/2 traffic an SSE stream can ride on, plus WebSocket connections and gRPC calls.

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?

If the connection opens but no events arrive, the cause is almost always 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 no hop 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. Multiplexing removes the roughly six-connection-per-origin limit that browsers impose on HTTP/1.1, although servers and intermediaries can still enforce concurrent stream limits.

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