Developer Tools
SSE Event Stream Builder
Build, parse, and validate Server-Sent Events streams in your browser. Spec-correct text/event-stream output with server snippets and clear errors.
Events
Each event becomes a block of field lines followed by a blank line. Add multiple data lines to send a multi-line payload.
Event 1
Event name (the EventSource "event" listener target). Leave blank for the default "message" event.
Sets the "last event id" the client returns on reconnect via Last-Event-ID.
Reconnection delay in milliseconds. Must be a base-ten integer.
Emitted as ": text". Most servers send a periodic comment to keep proxies from closing the connection.
data lines
Each line is emitted as its own data: line. The client joins them with a newline before firing the event.
Generated stream
Send this as the response body with Content-Type: text/event-stream. Lines use LF.
event: message data: Hello from the server
Server and client snippets
Drop into your stack. The headers shown disable buffering at proxies (Nginx, Cloudflare) so the events flush in real time.
Raw stream (text/event-stream)
event: message data: Hello from the server
curl -N (consume the stream)
curl -N -H "Accept: text/event-stream" https://example.com/stream
Express.js (Node) writer
app.get("/stream", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache, no-transform");
res.setHeader("Connection", "keep-alive");
res.setHeader("X-Accel-Buffering", "no");
res.flushHeaders();
res.write(`event: message
data: Hello from the server
`);
res.end();
});Next.js Route Handler (App Router)
export async function GET() {
const body = "event: message\ndata: Hello from the server\n\n";
return new Response(body, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
"X-Accel-Buffering": "no",
},
});
}Fastify (Node) writer
fastify.get("/stream", (req, reply) => {
reply.raw.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
"X-Accel-Buffering": "no",
});
reply.raw.write(`event: message
data: Hello from the server
`);
reply.raw.end();
});Cloudflare Worker writer
export default {
async fetch() {
const body = "event: message\ndata: Hello from the server\n\n";
return new Response(body, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
},
});
},
};Vercel Edge Function writer
export const config = { runtime: "edge" };
export default async function handler() {
return new Response("event: message\ndata: Hello from the server\n\n", {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
},
});
}FastAPI (Python) writer
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
@app.get("/stream")
async def stream():
body = 'event: message
data: Hello from the server
'
return StreamingResponse(iter([body]), media_type="text/event-stream", headers={
"Cache-Control": "no-cache, no-transform",
"X-Accel-Buffering": "no",
})Go net/http writer
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache, no-transform")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
fmt.Fprint(w, `event: message
data: Hello from the server
`)
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
}Browser EventSource client
const es = new EventSource("https://example.com/stream");
es.addEventListener("token", (e) => {
const payload = JSON.parse(e.data);
console.log("delta", payload.delta);
});
es.addEventListener("done", () => es.close());
es.onerror = () => {
// The browser auto-reconnects unless es.close() was called.
};Nginx reverse proxy (do not buffer)
location /stream {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 1h;
add_header X-Accel-Buffering no always;
}Quick reference
Recognized fields
Only "event", "data", "id", and "retry" are recognized. Other field names are silently dropped by the spec, so put structured payloads inside data:.
Dispatch is the blank line
An event is dispatched whenever the parser sees a blank line. Without a trailing blank line after the last event, the buffered event is discarded.
Multi-line data
Multiple consecutive data: lines are joined with a newline before the event fires. Useful for JSON pretty-printed payloads.
id and Last-Event-ID
When the browser EventSource reconnects it sends the last id value as the Last-Event-ID request header. Use it to resume the stream.
retry is milliseconds
The retry: field sets the client reconnection delay in milliseconds. Non-integer values are silently ignored.
Disable proxy buffering
Send Cache-Control: no-cache and X-Accel-Buffering: no, and set proxy_buffering off in Nginx, so events flush instead of being held back.
How to use
- Pick a tab at the top: Build stream to compose an SSE response from a small form, or Parse and explain to inspect a stream you have on hand.
- In Build mode, edit the first event in place or click Add event to append more. Each row takes an event name, one or more data lines, an id, a retry value in milliseconds, and an optional keepalive comment.
- Read the Generated stream panel below and copy the raw output, or grab one of the server snippets for Express, Next.js, Fastify, Cloudflare Worker, Vercel Edge, FastAPI, Go net/http, or the matching Nginx reverse-proxy config.
- In Parse mode, paste a stream into the textarea. The tool runs the WHATWG parsing algorithm and lists every dispatched event with its name, data buffer, id, and retry, plus the effective Last-Event-ID and reconnection time.
- Check the Issues and notes panel for unrecognized fields, NULL bytes in id, non-integer retry values, and a final note when the stream is missing a trailing blank line so the last event is discarded.
About this tool
SSE Event Stream Builder is a browser workbench for the text/event-stream wire format defined by the WHATWG HTML Living Standard, Section 9.2.6 (Server-Sent Events). SSE is the protocol behind OpenAI Chat Completions and Anthropic Messages streaming, GitHub Copilot completions, the Vercel AI SDK, Next.js streaming responses, and most internal real-time notification APIs; it is also the format the browser EventSource interface speaks. The format is small but easy to get wrong, so the tool handles every rule in the spec instead of just printing a pretty version. Build mode is a per-event form that turns one or more events into a clean stream: each event takes an event name, one or more data lines (multiple data lines are joined with a newline by the client before the event fires), an id (which sets the client Last-Event-ID for the next reconnect), a retry value in milliseconds, and an optional keepalive comment that ships as a line starting with a single colon. The encoder emits a syntactically correct stream with LF terminators and a trailing blank line so the last event actually dispatches; it also warns when an id contains a NULL byte, when retry is not a base-ten integer, when an event name or id has a newline, when a keepalive comment starts with another colon, and when the total data crosses 256 KiB (which buffers in some proxies and CDNs). Ready-to-paste snippets cover curl -N for consuming, Express, Next.js Route Handler, Fastify, Cloudflare Worker, Vercel Edge Function, FastAPI, Go net/http for serving, the browser EventSource client for receiving, and a Nginx reverse-proxy config that disables buffering (proxy_buffering off, X-Accel-Buffering: no) so events flush in real time instead of being held back. Parse mode runs the WHATWG event-stream parsing algorithm character by character against any stream you paste: lines are split on CR, LF, or CRLF; a leading BOM is stripped; recognized fields (event, data, id, retry) update the in-band state; unknown fields are flagged as notes; comments are surfaced separately; the data buffer is dispatched only on the blank line that follows it; and a stream that ends without that trailing blank line is reported as a discarded event. Each dispatched event shows its index, source line, event name, data buffer, id, and retry, plus the effective Last-Event-ID and reconnection-time-ms used by EventSource. Useful for debugging why your AI streaming response stops dispatching, why proxies break SSE under load, why retry: 30s does not slow down reconnects, why id: with a NULL byte gets silently dropped, and why the browser fires message instead of your named event. Everything runs in your browser. The stream text never leaves the tab.
Free to use. Works in your browser. No signup, no login.
Related tools
You may also like
HTTP Headers Parser
Parse, classify, and decode HTTP headers, with a missing security headers audit.
Open tool
DeveloperCache-Control Header Builder
Build and parse Cache-Control headers with directive flags, max-age presets, conflict checks, and ready-to-paste server snippets.
Open tool
DeveloperCORS Headers Generator
Build Access-Control headers with live validation and Apache, Nginx, Vercel, Netlify, Next.js, Worker, and Express snippets.
Open tool
DevelopercURL Command Builder
Form-driven builder that emits curl, PowerShell, HTTPie, wget, fetch, and raw HTTP/1.1.
Open tool
DeveloperFetch to cURL Converter
Convert fetch() and axios calls to a copyable curl command for any shell.
Open tool
DeveloperContent-Disposition Header Builder
Build RFC 6266 download headers with UTF-8 filenames; parse and explain existing values.
Open tool