On this page
- The core difference
- SSE is a text event framing format over HTTP
- WebSockets establish a persistent bidirectional connection
- Why AI APIs often prefer HTTP streaming
- SSE is not the same as browser EventSource
- HTTP streaming can work without SSE
- Reconnect semantics are different
- WebSockets do not magically solve reconnect either
- Cancellation is simpler when one request owns one stream
- WebSockets make multiplexing easier
- Backpressure deserves explicit thought
- Proxies and infrastructure can influence the choice
- Mobile apps change the tradeoff
- Security is mostly above the transport layer
- Observability is often easier with request-scoped streams
- When SSE / HTTP streaming is a strong choice
- When WebSockets are a strong choice
- Do not proxy SSE into WebSockets without a reason
- A practical decision table
- Testing checklist
- Where BYOKchat fits
- Further reading
Most AI chat APIs stream one request in one direction: the client sends a request, then the server incrementally sends text, reasoning, tool-call arguments, usage updates, and completion events back.
That shape is a strong fit for ordinary HTTP response streaming and, on the web, often for Server-Sent Events (SSE). WebSockets are useful when the application genuinely needs a long-lived bidirectional channel, but they are not automatically a better streaming transport just because they are lower-level and full duplex.
The practical rule is:
Use the simplest transport that matches the protocol you actually have. Do not introduce a WebSocket layer just to make an HTTP streaming API feel more “real time.”
The core difference
SSE and WebSockets solve different communication shapes.
SSE / HTTP response stream
client ── request ──> server
client <─ events ─── server
WebSocket
client <===========> server
full duplex
For many AI generations, the first shape is enough.
The client sends one request body containing:
- model;
- messages/input;
- tools;
- generation settings;
- attachment references;
- reasoning options.
The server then emits a stream until the response finishes, fails, or is cancelled.
SSE is a text event framing format over HTTP
SSE is not a new transport protocol. It is an event framing format conventionally delivered with:
Content-Type: text/event-stream
An event might look like:
event: output_text_delta
data: {"delta":"Hel"}
followed by:
event: output_text_delta
data: {"delta":"lo"}
The blank line ends one event.
The HTML standard defines event streams as UTF-8 and supports fields including:
event;data;id;retry;- comment lines beginning with
:.
Multiple data: lines in one event are joined with newline characters.
See the WHATWG Server-Sent Events specification.
WebSockets establish a persistent bidirectional connection
A WebSocket begins with an HTTP upgrade handshake and then becomes a framed full-duplex connection.
After the connection opens, both sides can send messages independently:
client -> server: start_generation
server -> client: token_delta
client -> server: update_presence
server -> client: tool_status
client -> server: cancel_generation
That can be valuable for collaborative apps, shared sessions, multiplexed real-time state, voice, presence, or systems where the server must initiate unrelated events at arbitrary times.
But a basic AI completion does not inherently need that flexibility.
Why AI APIs often prefer HTTP streaming
HTTP streaming has several practical advantages for model APIs:
- it follows the request/response ownership model naturally;
- authentication is attached to the request in a familiar way;
- request cancellation maps to aborting the request;
- infrastructure already understands HTTP status codes and headers;
- proxies, API gateways, logging, and observability usually fit HTTP well;
- one failed generation does not necessarily invalidate a shared long-lived channel;
- each request can use independent model, timeout, and authorization settings.
A provider can also expose structured events without requiring clients to implement a separate socket protocol.
SSE is not the same as browser EventSource
This distinction matters for AI clients.
The browser EventSource API is one consumer for text/event-stream, but many AI clients parse SSE manually from a fetch() response or native HTTP stream.
Why?
Because model requests are usually POST requests with JSON bodies and authorization headers.
The classic browser EventSource interface is designed around opening an event stream URL rather than sending an arbitrary POST body with custom request semantics.
So a web AI client commonly does:
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(request),
signal,
});
for await (const chunk of readBody(response.body)) {
parser.feed(chunk);
}
The application uses SSE framing, but not necessarily the EventSource object.
HTTP streaming can work without SSE
Some providers stream newline-delimited JSON, JSON fragments, or other event formats.
So separate two questions:
- transport — HTTP response stream or WebSocket?
- framing — SSE, NDJSON, binary frames, provider-specific events?
Do not hard-code “streaming means SSE” into the core architecture.
A provider adapter can expose neutral events such as:
type StreamEvent =
| { type: "text_delta"; text: string }
| { type: "reasoning_delta"; text: string }
| { type: "tool_delta"; callId: string; bytes: string }
| { type: "usage"; inputTokens?: number; outputTokens?: number }
| { type: "completed" }
| { type: "failed"; error: Error };
without pretending the underlying wire format is identical.
Reconnect semantics are different
The browser EventSource model includes automatic reconnect behavior and Last-Event-ID support.
That does not mean every AI stream can be safely resumed after a disconnect.
A generation request may be:
POST request
→ provider starts inference
→ stream emits 1,200 tokens
→ network drops
Reopening the same URL may start a new generation instead of resuming the old one.
The ability to reconnect at the transport level is not the same as semantic resumability.
For AI generation, you need provider/application support for concepts such as:
- durable response IDs;
- replayable events;
- polling completed background responses;
- continuation from a known provider object;
- idempotent request creation.
See How to Resume or Recover an Interrupted AI Generation.
WebSockets do not magically solve reconnect either
A WebSocket connection can also disappear.
If the connection held only ephemeral in-memory state, reconnecting may leave the client asking:
Did the model finish?
Did the server execute the tool?
Was the last message acknowledged?
Should I send the request again?
Reliable recovery requires application-level state regardless of transport.
A robust WebSocket protocol may need:
- connection/session identifiers;
- monotonically increasing sequence numbers;
- acknowledgement rules;
- replay windows;
- durable operation IDs;
- duplicate detection;
- reconnect handshakes.
Those are protocol responsibilities, not free properties of WebSockets.
Cancellation is simpler when one request owns one stream
With an HTTP streaming request, cancellation can often be modeled as:
user taps Stop
→ client aborts request
→ adapter marks local generation cancelled
→ any provider-side cancellation is attempted if supported
With a shared WebSocket, cancelling one generation requires a protocol message such as:
{
"type": "cancel",
"operation_id": "op_123"
}
Then the server must distinguish:
- cancel received;
- cancel accepted;
- generation already finished;
- tool already executed;
- connection closed before acknowledgement.
That is perfectly workable, but it is more protocol surface.
WebSockets make multiplexing easier
Suppose one connection carries:
chat A generation
chat B generation
presence updates
workspace events
voice audio
background task status
WebSockets can be a good fit because every message can include a channel or operation ID.
For example:
{
"stream": "chat_A",
"type": "text_delta",
"delta": "hello"
}
But multiplexing also means one connection becomes shared infrastructure. A parser bug, stalled consumer, authentication issue, or socket reconnect can affect many concurrent operations.
Backpressure deserves explicit thought
A streaming client consumes data at some rate.
If the producer emits faster than the consumer can parse/render/persist, buffering grows.
Classic browser WebSocket does not provide built-in application backpressure. MDN explicitly warns that messages arriving faster than the application can process them can cause memory growth or CPU pressure.
HTTP body streams and modern stream APIs can expose pull-based reading and natural buffering boundaries more directly.
In AI text generation, provider token rates are usually much lower than raw network throughput, but rendering work can still become expensive if the client updates the entire transcript for every tiny delta.
Backpressure is therefore often a UI and parsing issue before it becomes a network issue.
Useful mitigations include:
- coalescing tiny deltas before rendering;
- parsing off the main UI thread where appropriate;
- batching persistence;
- limiting expensive Markdown relayout frequency;
- keeping one append-only mutable buffer per active stream.
Proxies and infrastructure can influence the choice
Many corporate networks, CDNs, reverse proxies, and gateways handle ordinary HTTP traffic very well.
WebSockets are widely supported, but they may require explicit proxy configuration and long-lived connection handling.
SSE/HTTP streaming can also fail behind buffering proxies if the intermediary waits for too much data before forwarding it.
So test the actual deployment path:
client
→ VPN / cellular network
→ CDN
→ reverse proxy
→ application server
→ provider
Important infrastructure behaviors include:
- buffering;
- idle timeouts;
- maximum request duration;
- connection reuse;
- HTTP/2 or HTTP/3 behavior;
- proxy support for upgrade connections;
- load balancer affinity for stateful socket servers.
Mobile apps change the tradeoff
On mobile, long-lived connections interact with:
- app backgrounding;
- radio power states;
- network changes between Wi-Fi and cellular;
- process suspension;
- OS networking policies.
A WebSocket that is useful while the app is foregrounded may not remain meaningful after the app is suspended.
Likewise, an HTTP stream can be interrupted when the process loses execution time.
For long-running jobs, durable background operations plus later reconciliation are often better than assuming any foreground stream will remain alive indefinitely.
See How Long-Running AI Tasks Work.
Security is mostly above the transport layer
TLS protects both HTTPS streaming and secure WebSockets (wss://).
The bigger security questions are usually:
- where credentials are attached;
- whether the connection is authenticated for one user or one operation;
- whether authorization is re-evaluated for privileged messages;
- whether message payloads are logged;
- whether reconnect can bind to the wrong session;
- whether tool execution has separate approval checks.
Do not infer that a persistent socket is safer because credentials are sent only once. A compromised authenticated channel remains privileged until it expires or is revoked.
Observability is often easier with request-scoped streams
One HTTP request naturally gives you:
request ID
provider/model
start time
HTTP status
TTFT
stream duration
finish reason
usage
error
With one multiplexed WebSocket, you need your own correlation layer for every operation.
That is not a reason to reject WebSockets, but it is additional engineering.
When SSE / HTTP streaming is a strong choice
Prefer request-scoped HTTP streaming when:
- one request produces one response stream;
- the provider already exposes HTTP streaming;
- the client mostly receives data after sending one request;
- standard auth/status/header semantics are useful;
- independent cancellation/timeouts per request matter;
- infrastructure is optimized for HTTP APIs.
This matches many text-generation APIs very well.
When WebSockets are a strong choice
Consider WebSockets when:
- both sides frequently initiate messages;
- one persistent connection multiplexes many operations;
- presence or collaboration is part of the product;
- real-time voice/audio is involved;
- server-initiated updates are first-class;
- the server protocol is explicitly designed around durable socket sessions.
The key is that the application needs WebSocket semantics, not merely “streaming.”
Do not proxy SSE into WebSockets without a reason
A common architecture mistake is:
provider SSE
→ backend parses events
→ backend repackages into WebSocket
→ browser parses WebSocket messages
Sometimes this is necessary—for example, because the backend also multiplexes tools, collaboration, or provider credentials.
But if the proxy exists only because WebSockets sound more real-time, you have created:
- another framing layer;
- another reconnect protocol;
- another buffering point;
- more state to observe;
- more failure modes.
Keep the original transport when it already fits the product.
A practical decision table
| Requirement | HTTP/SSE | WebSocket |
|---|---|---|
| One request → one streamed answer | Excellent | Works, often unnecessary |
| Full duplex messaging | Limited | Excellent |
| Standard HTTP status/header semantics | Excellent | Mainly during handshake |
| Independent request cancellation | Natural | Requires operation protocol |
| Multiplex many logical channels | Multiple requests | Natural with message IDs |
| Browser-native reconnect semantics | EventSource supports it | App protocol required |
| Resume AI generation semantics | Requires app/provider support | Also requires app/server support |
| Simple API gateway integration | Usually straightforward | May need socket-aware config |
| Voice/presence/collaboration | Usually awkward | Strong fit |
Testing checklist
Test whichever transport you choose under:
- slow networks;
- mid-stream disconnects;
- zero-byte responses;
- partial UTF-8 boundaries;
- proxy idle timeout;
- server restart;
- user cancellation;
- background/foreground transitions;
- duplicate or reordered application messages where applicable;
- authentication expiry;
- concurrent generations;
- very long responses;
- tool-call streams.
Where BYOKchat fits
A multi-provider BYOK client should not make UI code care whether the underlying provider uses SSE, another HTTP event format, or a different native stream representation. Provider adapters can normalize wire events into a small internal stream model while preserving provider-specific semantics such as tool-call boundaries and reasoning events.
That keeps the transport choice where it belongs: inside the integration layer.