SSE Stream Inspector
Paste a raw server-sent event stream and see the events a compliant client would actually dispatch, plus the framing mistakes that silently drop one.
Everything on this page runs in your browser. Nothing is uploaded, logged or sent anywhere — your input is kept in the address bar so a link reopens it, which also means anything you paste travels with the link. Do not share a link to a payload you would not publish.
The stream does not end with a line terminator. Counted anyway, but a live connection that ends this way has an undispatched buffer.
The harbour
- Events dispatched
- 5
- Comment lines (`:` …)
- 1
- Events carrying JSON
- 3
- Events whose data failed to parse
- 1
- Named event types
- message, token
- Last event ID
- 42
- Characters reassembled
- 12
Decoded events
| # | event | id | data |
|---|---|---|---|
| 0 | message | — | {"id":"1","choices":[{"delta":{"role":"assistant","content":""}}]} |
| 1 | message | — | {"id":"1","choices":[{"delta":{"content":"The "}}]} |
| 2 | token | 42 | {"id":"1","choices":[{"delta":{"content":"harbour "}}]} |
| 3 | message | 42 | {"id":"1","choices":[{"delta":{"content":"is "}}]}
{"id":"1","choices":[{"delta":{"content":"closed."}}]}2 `data:` lines with no blank line between them. Per the specification that is ONE event whose data is those lines joined by newlines — not 2 events. Each line parses on its own; together they are not JSON. Put a blank line after every event. |
| 4 | message | 42 | [DONE] |
Problems in the stream
- check`retry: 3s` is not a number of millisecondsline 14, column 1
`retry` takes ASCII digits only and is silently ignored otherwise, so the reconnection delay you think you set is not set. `3s` is a common way to get this wrong.
retry: 3s ^
- must fixThe last event was never dispatchedline 16, column 1
The stream ends with data buffered and no blank line after it. A compliant client discards that buffer — the event never fires. This is the single most common SSE bug: the server writes the final chunk and closes the connection without the terminating blank line, and the last token of every response goes missing.
data: [DONE] ^
- note`[DONE]` sentinel present
A convention, not part of the event-stream specification. A spec-compliant client dispatches it as an ordinary message event whose data is the five characters `[DONE]`, so your handler has to special-case it before trying to parse it as JSON.
- must fixEvent 3: data is not valid JSONline 11, column 1
2 `data:` lines with no blank line between them. Per the specification that is ONE event whose data is those lines joined by newlines — not 2 events. Each line parses on its own; together they are not JSON. Put a blank line after every event.
data: {"id":"1","choices":[{"delta":{"co… ^
: are comments, the data buffer keeps its internal newlines and loses one trailing newline at dispatch, and the last event ID persists to the next event while the event type does not. It cannot see anything the transport did to you — chunked framing, a proxy buffering until the response completes, or gzip that was never flushed. If your stream arrives all at once instead of incrementally, the problem is not in this text.What the framing rules actually are
Server-sent events is a small specification with four field names — data, event, id, retry — and every other field name is dropped in silence. Events are separated by a blank line, not by a newline. Multiple data: lines in one event are joined with newlines between them, which is how a JSON payload can span lines legally. And an event whose data buffer is empty is not dispatched at all, which means event: ping on its own with no data does nothing.
The bug this page exists for
A stream that ends without a blank line after its last data: leaves that data buffered forever. The server sent it; the client is required to discard it. Everything looks fine in a log and in curl, because both show you the bytes rather than the events, and the symptom is that the last few characters of every response are missing — reliably, and only in production, where a proxy is more likely to close the connection promptly. If the report above says the last event was never dispatched, that is your bug and it is one line of server code.
Reassembling the text
The path field pulls one string out of every event's JSON and concatenates them in order, which is how a chat stream turns back into a message. It is worth doing even when the stream looks fine: a duplicated chunk, a chunk delivered out of order, or an event whose data failed to parse shows up immediately in the joined text and is invisible in the event list. The path is yours to type because every provider nests it differently, and this page does not ship anyone's schema.