Also available in: Español · Português · Français · العربية
Server-sent events parser: see which events actually fire
Paste an event stream and get the events it dispatches, the last event ID at each one, and the reason every other line did nothing.
What are server-sent events?
Server-sent events are the simple half of realtime on the web: the server holds a response open and writes lines of text down it, the browser turns each block of lines into an event, and your code listens. No handshake, no framing, no library — a response with the content type text/event-stream and a body of lines like data: something. It is what most streaming APIs use to push tokens as they are generated, and it is a plain enough format that people write servers for it by hand.
Writing it by hand is where the trouble starts, because the format is deceptively strict. The parsing algorithm is specified completely, down to which buffer holds what and when each one is emptied, and several of its rules are the opposite of what the shape of the syntax suggests. A stream can look perfectly reasonable, contain no errors anywhere, and deliver nothing.
This page runs that algorithm on whatever you paste. It shows the events that come out, with the last event ID as the parser would hold it at that moment, and it shows every line that contributed nothing along with the reason. That second half is usually the one you came for.
How to use it
- Paste the stream body. The response body as it went over the wire, not the headers. Lines may end with a line feed, a carriage return, or both — all three are allowed, and the sample buttons include a stream that uses each.
- Read the events that fired. Each one with its type, its data, and the last event ID at the moment it dispatched. If the table is empty, the block below explains which line was responsible.
- Check the lines that did nothing. Comments, unknown fields, a field name in the wrong case, an id containing a null, a retry that is not a number, and any block that ended without dispatching. Each row says which of those it was.
The rule that catches everyone
An event with no data is not dispatched. The specification is explicit: if the data buffer is an empty string when a blank line arrives, the parser empties the buffers and returns without producing anything. So a server that sends an event field and a blank line — a perfectly natural way to write a heartbeat — fires no listener, logs no error, and shows nothing in the network panel except a stream that appears to be working.
The fix is either a data field, even an empty one, or a comment. An empty data field is enough, because the parser appends a newline to the buffer for every data line, so the buffer is no longer empty even when the value is. A comment line is a colon followed by anything, and is the conventional way to keep a connection alive: it resets nobody's timers and produces nothing.
The other half of the same rule is easy to miss in the other direction. The event type buffer is emptied on every dispatch, so an event with no event field is a message. The last event ID buffer is not — the specification says in as many words that it is not reset, so it keeps its value until the server sets it again. Two buffers, side by side in the same algorithm, with opposite lifetimes. An event with no id inherits the previous one, and that is what gets sent back in the Last-Event-ID header when the connection drops and the browser reconnects.
Four smaller rules worth knowing
Exactly one leading space is removed from a value. One. The line data: hello and the line data:hello mean the same thing, but data: hello with two spaces keeps one of them. That is a common way to get a stray space into a JSON payload that then fails to parse for no visible reason.
Field names are compared literally, with no case folding. Data is not data. A capitalised field name is not an error and is not reported anywhere — it is simply an unknown field, and unknown fields are ignored. The same goes for anything misspelled: there are exactly four field names, and everything else is silently dropped.
An id whose value contains a null character is ignored entirely. Not truncated at the null, not blanked — the buffer keeps whatever it held before, so the next event inherits the older id. And a retry is applied only if its value is made only of digits, which rules out a negative number, a decimal, and anything with a unit attached.
Finally, a stream that stops mid-event loses that event. The specification says any pending data must be discarded when the end is reached, so a block with a data line but no blank line after it never arrives. This tool distinguishes that from a stream that stops mid-line, where the last characters never became a line at all and were never even looked at.
What this tool does not do
It does not connect to anything. You paste a body; the page reads it. To capture a real stream, the network panel in your browser will show the response as it arrives, and a command-line HTTP client will print it as it comes.
It reads a complete stream rather than a live one, which makes one case different from a real client. A stream ending in a bare carriage return is unambiguous here, because there is nothing after it; a streaming parser receiving the same bytes has to hold that carriage return in case the next chunk starts with a line feed, since the two together are a single line ending. Both behaviours are correct for what they are.
It also does not check that your data is valid JSON, or anything else. The format carries text and takes no interest in what the text means — which is worth remembering, because a data field split across several lines is rejoined with newlines between the pieces, and that is a legal way to send a formatted JSON document that many hand-written servers produce by accident.
Why is it free?
Reading lines out of a string is something your browser does instantly. There is no server in the path, so there is nothing to meter and no account to create.
Nothing is uploaded. The stream you paste stays in this tab.