BYOKchat Blog

SSE vs WebSockets for AI Streaming

Compare Server-Sent Events and WebSockets for AI streaming, including transport direction, reconnect behavior, proxies, mobile clients, backpressure, and cancellation.

· 8 min read

On this page
  1. The core difference
  2. SSE is a text event framing format over HTTP
  3. WebSockets establish a persistent bidirectional connection
  4. Why AI APIs often prefer HTTP streaming
  5. SSE is not the same as browser EventSource
  6. HTTP streaming can work without SSE
  7. Reconnect semantics are different
  8. WebSockets do not magically solve reconnect either
  9. Cancellation is simpler when one request owns one stream
  10. WebSockets make multiplexing easier
  11. Backpressure deserves explicit thought
  12. Proxies and infrastructure can influence the choice
  13. Mobile apps change the tradeoff
  14. Security is mostly above the transport layer
  15. Observability is often easier with request-scoped streams
  16. When SSE / HTTP streaming is a strong choice
  17. When WebSockets are a strong choice
  18. Do not proxy SSE into WebSockets without a reason
  19. A practical decision table
  20. Testing checklist
  21. Where BYOKchat fits
  22. 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:

  1. transport — HTTP response stream or WebSocket?
  2. 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

RequirementHTTP/SSEWebSocket
One request → one streamed answerExcellentWorks, often unnecessary
Full duplex messagingLimitedExcellent
Standard HTTP status/header semanticsExcellentMainly during handshake
Independent request cancellationNaturalRequires operation protocol
Multiplex many logical channelsMultiple requestsNatural with message IDs
Browser-native reconnect semanticsEventSource supports itApp protocol required
Resume AI generation semanticsRequires app/provider supportAlso requires app/server support
Simple API gateway integrationUsually straightforwardMay need socket-aware config
Voice/presence/collaborationUsually awkwardStrong 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.

Further reading

Keep reading