SSE · Streaming · Testing
How to test Server-Sent Events (SSE) endpoints online
A practical guide to testing text/event-stream endpoints with Swastik Codes: connecting, filtering event types, resuming with Last-Event-ID, reconnection, and POST-based streams such as LLM APIs.
To test a Server-Sent Events endpoint you need a client that sends the request, keeps the response open, parses the text/event-stream format as it arrives and shows each event with its type, id and data. In Swastik Codes you create a Server-Sent Events request, paste the URL, add any headers or auth, choose the HTTP method (GET, or POST for streaming APIs that take a body), and press Connect. Events appear in the log as they arrive.
The browser's own EventSource is a poor testing tool: it only does GET, it cannot set custom headers such as Authorization, and it hides the raw stream. This guide covers what a proper SSE client shows you, how to test resumption and reconnection, and how to test POST-based streams like the ones large language model APIs use.
What you need
- An SSE endpoint reachable from the public internet. The Swastik Codes web app relays the connection through the swastik.codes server, which refuses private, loopback and other reserved addresses, so
http://localhost:3000/eventscannot be reached from the web app. The desktop app for Windows and Linux connects from your own machine, so it reaches local endpoints. - A free Swastik Codes account, because streaming connections run through the server.
If you want to try the client first, Wikimedia's public EventStreams service is a real SSE endpoint: https://stream.wikimedia.org/v2/stream/recentchange streams every edit on Wikimedia projects as it happens. It is a busy stream, so expect many events per second, and disconnect when you are done.
How the SSE format works
An SSE response has the content type text/event-stream and is a sequence of events separated by blank lines. Each event is made of fields:
event: order.updated
id: 1042
data: {"orderId": "A-17", "status": "shipped"}
: this line is a comment, often used as a heartbeat
retry: 5000
data: a message without an event type is a "message" event
data: and multiple data lines are joined with newlines
data:is the payload. Severaldata:lines in one event are joined with\n.event:is the event type. Without it, the type ismessage.id:sets the last event id. A client that reconnects sends it back in theLast-Event-IDrequest header so the server can resume.retry:tells the client how many milliseconds to wait before reconnecting.- Lines starting with
:are comments and are ignored.
Swastik Codes parses the stream incrementally following the WHATWG event stream rules, including events that arrive split across network packets and UTF-8 characters split in the middle.
Step 1: create an SSE request
- Open the app and sign in.
- Click + in the tab bar and choose Server-Sent Events from the New request menu.
- Paste the URL, for example
https://stream.wikimedia.org/v2/stream/recentchange. - Leave the method on GET and press Connect.
The client sends Accept: text/event-stream and Cache-Control: no-cache for you. If the server answers with a different content type, the log shows a note and the body is parsed as an event stream anyway, which helps when a misconfigured proxy rewrites the header.
Each event in the log shows its type, data, id and arrival time. Use View as to switch between automatic detection, text and JSON, search the log, and select two events to see the time between them. Connection details shows the request and response headers, which is where you check for proxy problems such as a missing Content-Type or an unexpected Content-Encoding.
Step 2: headers, auth and parameters
Most private SSE endpoints authenticate with a header. The Authorization tab supports Bearer, Basic, API key and OAuth 2.0, or inheriting auth from the collection. Headers takes any other header, and Params edits the query string. Use variables like {{baseUrl}} and {{token}} so the same request works across environments.
Step 3: filter event types
Busy streams mix many event types. Type the types you care about into the event filter, comma separated (for example order.updated, order.cancelled), and only those are shown. The filter only affects the display; the connection still receives everything.
Step 4: test resumption with Last-Event-ID
Resumption is the feature that makes SSE more than a long HTTP response, and it is the part most often broken in server implementations. To test it:
- Connect and let a few events with ids arrive.
- The id of the most recent event is shown above the log with a Use button. Click it to copy that id into the Last-Event-ID setting.
- Disconnect, wait a moment, and connect again.
The client now sends Last-Event-ID: <id> with the request. A correct server sends the events after that id and nothing before it. Check for gaps and duplicates at the boundary. You can also type any id into Last-Event-ID by hand, for example an old id to test what the server does when the id is too old to replay.
Step 5: test reconnection and retry
In Settings, turn on Reconnect when the stream ends (honours retry:). When the server closes the stream, the client waits and reconnects with the last id it saw, just like EventSource does. The wait starts at the Reconnect delay (ms) and switches to the server's retry: value once one arrives. Reconnection attempts limits the number of tries (0 means unlimited). The log shows each reconnect with the delay and the Last-Event-ID that was sent.
Other settings: Connection timeout (ms) for the initial response, Maximum event size (MB) (larger events are discarded and reported, so a stream that never sends a newline cannot grow without bound), and server certificate verification.
Step 6: POST streams and LLM-style APIs
Many APIs stream with SSE in response to a POST request with a JSON body. Chat completion endpoints of large language model providers are the best-known example: you POST a prompt with "stream": true and receive tokens as data: events, often ending with a sentinel such as data: [DONE]. EventSource cannot do this at all.
In Swastik Codes, pick POST (or PUT, PATCH, DELETE) in the method dropdown next to the URL, then open the Body tab and write the JSON body:
{
"model": "{{model}}",
"stream": true,
"messages": [{ "role": "user", "content": "Explain Server-Sent Events in one sentence." }]
}Add the provider's auth header (usually a bearer token or an API key header) and a Content-Type: application/json header, then press Connect. Each chunk arrives as an event, so you can see exactly how the stream is split, how long the first token takes, and how the stream ends. Keep API keys in an environment variable rather than in the request, especially in shared workspaces.
A small SSE server to test against
This Node.js server uses only the standard library. GET /events sends numbered events with ids, honours Last-Event-ID and sends a heartbeat comment. POST /stream streams back the words of the text field from the JSON body, then a done event.
// server.mjs (Node.js 18+, no dependencies)
import { createServer } from 'node:http';
const headers = {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'X-Accel-Buffering': 'no', // stop nginx from buffering the stream
};
createServer(async (req, res) => {
if (req.method === 'GET' && req.url.startsWith('/events')) {
res.writeHead(200, headers);
let id = Number(req.headers['last-event-id']) || 0;
res.write('retry: 3000\n\n');
const tick = setInterval(() => {
id += 1;
const type = id % 5 === 0 ? 'order.cancelled' : 'order.updated';
res.write(`event: ${type}\nid: ${id}\ndata: ${JSON.stringify({ seq: id, at: new Date().toISOString() })}\n\n`);
}, 1000);
const heartbeat = setInterval(() => res.write(': ping\n\n'), 15000);
req.on('close', () => { clearInterval(tick); clearInterval(heartbeat); });
return;
}
if (req.method === 'POST' && req.url === '/stream') {
let raw = '';
for await (const chunk of req) raw += chunk;
const { text = 'hello from the stream' } = JSON.parse(raw || '{}');
res.writeHead(200, headers);
for (const word of String(text).split(/\s+/)) {
res.write(`data: ${JSON.stringify({ token: word })}\n\n`);
await new Promise((r) => setTimeout(r, 200));
}
res.write('event: done\ndata: [DONE]\n\n');
return res.end();
}
res.writeHead(404).end();
}).listen(Number(process.env.PORT) || 3000);Deploy it and try:
GET https://your-host/events: events arrive every second, every fifth one anorder.cancelled. Filter onorder.cancelled.- Click Use next to the last id, disconnect, wait five seconds and reconnect. The sequence continues from that id.
- Turn on reconnection and restart the server: the client waits
retrymilliseconds and resumes. POST https://your-host/streamwith body{"text": "one two three four"}: fourdataevents and adoneevent.
Troubleshooting
Nothing arrives for a long time, then everything at once. A proxy is buffering the response. For nginx, set proxy_buffering off; for the location or send X-Accel-Buffering: no from the app. Compression middleware can also buffer: exclude text/event-stream from it.
The connection drops after 30 or 60 seconds. An idle timeout on a load balancer or proxy. Send a comment line (: ping) every 15 to 30 seconds.
Events show up with the type message when you expected a name. The server is not sending an event: field, or it is sending it after data: in a different event block. Each event ends at a blank line.
JSON data is cut in half. The server wrote a raw newline inside the data. Each line of the payload needs its own data: prefix, or serialise the JSON on one line.
Connection refused for a private address. The relay blocks private and loopback addresses on purpose. Use a public hostname.
Frequently asked questions
What is the difference between SSE and WebSocket?
SSE is one-way, server to client, over plain HTTP, with built-in resumption through event ids. WebSocket is a two-way channel with its own framing. If clients only need to receive updates, SSE is simpler to run behind ordinary HTTP infrastructure. See how to test a WebSocket API for the other side.
Can I send custom headers to an SSE endpoint?
Yes. Unlike the browser's EventSource, Swastik Codes can send any headers, including Authorization, and supports Bearer, Basic, API key and OAuth 2.0 auth.
Can I test SSE streams that use POST?
Yes. Choose POST, PUT, PATCH or DELETE next to the URL and write the request body on the Body tab. This covers streaming LLM APIs and other POST-based streams.
How do I test Last-Event-ID resumption?
Connect, click Use next to the last id shown above the log, disconnect and reconnect. The client sends that id in the Last-Event-ID header. You can also type an id into the setting by hand.
Does MCP use SSE?
Some MCP servers use SSE as a transport, either the older HTTP+SSE transport or streamed responses in the Streamable HTTP transport. For those, use the dedicated MCP request type described in how to test an MCP server, which handles the MCP handshake for you.
Try it
Open Swastik Codes, create a Server-Sent Events request and connect to your endpoint, or to Wikimedia's public stream to see one in action. You need a free account to connect. The SSE client feature page lists every option, and the REST API client covers the non-streaming endpoints of the same API.