1. Topics
  2. /
  3. Protocols
  4. /
  5. Server-Sent Events alternatives
24 min read•Updated Sep 30, 2026

When Server-Sent Events is not enough: your alternatives

You run Server-Sent Events (SSE) in production, and your product now needs the client to send data back while the stream is open. A cancel signal, an acknowledgment, a filter change. But SSE can't carry messages from the client to the server, because the protocol defines one direction only.

Four alternatives give the client a return path: a way to send messages to the server while the stream stays open. But each alternative has a cost: you either lose a capability SSE already gives you, or lose the ordering between client messages and the stream. This page sets out what each alternative provides, what each one takes away, and how to decide which to adopt, including when staying on SSE is the better call.

Copy link to clipboard

Key takeaways

  • SSE reconnects automatically and passes through most proxies and firewalls, with no code from you. Adding an HTTP POST endpoint beside the stream keeps both capabilities, and every other alternative requires you to rebuild reconnection, firewall traversal, or both.

  • Two questions decide between SSE plus POST and WebSockets: how often clients send, and whether client messages must be ordered against server events. Occasional, user-triggered messages fit SSE plus POST, while continuous or ordered messages need one connection, such as a WebSocket.

  • No reliable public data shows how often networks block WebSockets. Test from your own clients, and treat failures in a few key accounts as more important than the overall failure rate.

  • Replay, delivery guarantees, and fan-out across servers are your work with every alternative, including SSE. The choice is between building and operating them yourself, or buying a managed platform.

Copy link to clipboard

What does SSE already do for you?

SSE is usually credited with three capabilities: reconnection, firewall traversal, and message recovery.

Copy link to clipboard

Reconnection is built into the browser

When an SSE connection drops, the browser's EventSource API reopens the connection automatically, without any application code. Your server can set the delay before each retry by sending a retry field in the stream. The HTML Living Standard specifies the reconnection behavior, so every modern browser reconnects automatically without a library.

Automatic reconnection in SSE has two limits. EventSource stops retrying permanently if the server responds with an HTTP error status, or with a content type other than text/event-stream. So a single 503 response during a deploy disconnects the client until the page reloads. EventSource is also a browser API, so native mobile and desktop clients depend on third-party SSE libraries whose reconnection behavior varies.

Copy link to clipboard

Firewall traversal comes from plain HTTP

An SSE stream is an ordinary HTTP response that the server keeps open instead of closing. Proxies, firewalls, and load balancers between your client and your server pass the stream along without needing to understand a new protocol.

Plain HTTP does not get SSE through every network. Some corporate proxies and antivirus products hold back an HTTP response until the server finishes sending the response. An SSE response never finishes, so clients behind those proxies receive events late, in batches, or not at all.

If some of your clients already report delayed or batched events, a buffering proxy is the likely cause. Long polling, covered below, is the usual fix.

Copy link to clipboard

Message recovery is only half built into SSE

Reconnection and message recovery are separate capabilities, but often conflated. In SSE, reconnection is automatic: the browser opens a new connection with no application code. Message recovery delivers the events the client missed while the connection was down. SSE only handles the client's half: when each event carries an id field, the browser sends the last ID received in a Last-Event-ID header on reconnect. So nothing resends the missed events unless your server has a replay store: storage that takes an event ID and returns every event published after that ID.

If your application recovers missed events today, your team built the replay store, and SSE contributed only the Last-Event-ID header. Before you plan a migration, confirm that your server reads the Last-Event-ID header, since broken message recovery is easy to miss. The answer tells you whether you already have message recovery to preserve, or a gap to close - whichever transport you choose.

Copy link to clipboard

Where does SSE stop?

SSE has four limits that matter when you evaluate alternatives: no return path, a six-connection cap per host, text-only payloads, and no delivery guarantees. HTTP/2 removes the connection cap, and application code can add delivery guarantees. No change to SSE adds a return path or native binary support.

Copy link to clipboard

The client can't send data on an SSE connection

EventSource sends one GET request, with no request body and no custom headers, as MDN documents. Any data the client sends has to travel in the URL or on a separate HTTP request. URLs are recorded in server logs, proxy logs, and browser history, so credentials such as auth tokens should not travel in a URL.

Workaround: none on the SSE connection itself. The client can send data on a separate HTTP request, but nothing orders that request against the events on the stream.

Credentials are a separate problem from sending data. Cookies do travel on the SSE request, so cookie-based auth works with EventSource. But the browser WebSocket API can't set custom headers either, so moving to WebSockets does not solve token-based auth.

Copy link to clipboard

Browsers limit HTTP/1.1 to six connections per host

Browsers open at most six HTTP/1.1 connections to one host name. All tabs open on your site share those six connections, along with every API call and image request to the same host. Each SSE stream holds one connection for as long as the stream stays open.

A user with four tabs open, each running a stream, has two connections left for every other request to your site, so page loads and API calls slow down. Chromium closed a request to raise the limit as "Won't fix", pointing to HTTP/2 as the fix instead.

Workaround: serve SSE over HTTP/2. Over HTTP/2, all streams to a host share one TCP connection, and the six-connection limit no longer applies. Whether you already use HTTP/2 depends on what your CDN or load balancer negotiates with the browser.

Copy link to clipboard

SSE carries text payloads only

Binary data has to be Base64-encoded, which makes each payload about 33% larger than the raw bytes. Your server encodes and your client decodes every binary message.

Workaround: none in practice. Base64 encoding works, but the extra size and processing remain. If most of your traffic is binary, SSE is the wrong transport regardless of the return path.

Copy link to clipboard

SSE has no delivery guarantees

SSE is a transport, not a messaging system. Nothing acknowledges receipt, so your server can't know which events a client received.

Nothing removes duplicates either, so a replay from your replay store can deliver the same event twice. Events arrive in order only within one unbroken connection.

Workaround: build acknowledgments, message IDs, and deduplication into your application, which is a significant undertaking. SSE has no return path for acknowledgments, and your server has to track what each client has acknowledged and redeliver the rest after a reconnect. Every client you ship, whether web, iOS, or Android, also needs code to discard duplicates.

Delivery code then needs ongoing maintenance. Delivery failures appear only under network conditions that are hard to reproduce, so every server and client release needs testing against dropped connections. Like the replay store, delivery code works with any transport, so the maintenance cost moves with you through a migration.

Copy link to clipboard

How to compare the SSE alternatives against each other

Four capabilities separate the alternatives from each other. The comparison table in the next section scores every alternative against all four, so you can see which capabilities each one trades away.

  1. Return path: can the client send on the stream's own connection? On one connection, the server's reply to a client message arrives at a known point in the stream. On a separate request, the client needs sequence numbers to work out which events came first.

  2. Reconnection: does the client reopen a dropped connection without application code? Rebuilt reconnection is hard to test, because the failures depend on network conditions that you can't easily reproduce.

  3. Message recovery: does a reconnecting client receive the events it missed? Every alternative can use the same replay store, so the difference is how much reconnect code you write.

  4. Firewall traversal: does the transport connect through proxies you don't control? Network equipment can detect and reject the Upgrade request that opens a WebSocket, most often on locked-down networks in government, healthcare, education, and call centers.

    No reliable public figure shows how often networks block WebSockets. Open a trial WebSocket connection from your existing clients alongside the SSE stream, and count failures per customer account. The rest of this page calls that test the WebSocket trial.

Delivery guarantees are another consideration, but none of the four alternatives provides delivery guarantees out of the box. If you need acknowledgments, deduplication, and exactly-once delivery without building them yourself, consider a managed platform.

Copy link to clipboard

The four alternatives to SSE compared

Use the table below to build a shortlist, then read the sections that follow for your shortlisted options. The table shows which capabilities each alternative keeps, but not what rebuilding a missing capability costs.

A managed realtime platform is a fifth option, covered in a later section, because choosing a platform is a purchase decision rather than a protocol choice.

Option

Return path

Reconnection

Message recovery

Firewall traversal

SSE today (baseline)

None

Built into the browser

Your replay store, if you built one

Passes most networks as plain HTTP

SSE plus an HTTP POST endpoint

Separate request, not ordered against the stream

Unchanged, still built into the browser

Unchanged for the stream. A failed POST is lost unless your client retries the POST

Passes most networks, like SSE

WebSockets

Same connection, ordered

A library (Socket.IO, SignalR) or your own code

Your replay store, queried by reconnect code you add

The Upgrade request can be blocked. Libraries fall back to HTTP

WebTransport

Same connection, ordered within each stream

Your own code

Your replay store, queried separately for each stream

Unproven. WebTransport runs on UDP, which some networks restrict

Long polling

Separate request, not ordered against server events

Little code: every cycle opens a new request, but failed requests need retries

Your replay store, queried with a position marker the client tracks

Passes as plain HTTP, including through buffering proxies

Copy link to clipboard

SSE plus an HTTP POST endpoint

SSE plus POST keeps your SSE stream unchanged and adds an ordinary HTTP endpoint for client messages. SSE plus POST is a proven production pattern rather than a workaround: the Model Context Protocol's Streamable HTTP transport and the OpenAI and Anthropic APIs all pair POST requests with SSE responses.

Gains: a return path, with reconnection, message recovery, and firewall traversal unchanged.

Costs: SSE plus POST has three costs.

  1. No ordering. A client can't tell which events the server sent before processing a cancel, so output can continue after the user presses stop. The fix is a marker on the stream showing where the server processed each client message.

  2. Routing across servers. With more than one server, a client's cancel can reach a different server from the one holding the stream, and have no effect. A shared message bus, such as Redis pub/sub, or sticky sessions route the message to the right server.

  3. Request overhead. Every POST is a full HTTP request, which adds up once clients send several messages a second. At that rate, move to a persistent connection such as a WebSocket.

Choose if: your clients send in response to user actions, and your product can live with a few events arriving after a cancel, or you add the stream marker described above.

A WebSocket is one connection that both client and server can send on at any time, with messages ordered within the connection. For background on the protocol, see what WebSockets are and how they work.

Gains: a return path ordered against server events, and binary data without Base64 encoding.

Costs: the reconnection and firewall traversal that SSE provided by default. The Socket.IO library and Microsoft's SignalR both add reconnection and an HTTP fallback, though reconnection is opt-in in SignalR, so the work is usually adopting a library rather than writing both yourself. You still run and scale the connection servers, and the fallback needs sticky sessions on the load balancer once you run more than one server.

Choose if: your clients send continuously, ordering between client and server messages matters, and the WebSocket trial shows no failures in accounts you depend on.

WebTransport runs over HTTP/3 and carries multiple independent, reliable streams inside one connection, plus unreliable datagrams. Safari 26.4 added WebTransport in March 2026, joining Chrome and Firefox, so browser support is no longer the obstacle.

Gains: a return path ordered within each stream, unreliable delivery for messages you would rather drop than wait for, and streams that keep flowing when a packet on another stream is lost. HTTP/2 already multiplexes SSE streams, so multiplexing alone is not a reason to adopt WebTransport.

Costs: server support is largely missing, because nginx does not support WebTransport and the leading Node.js implementation calls its own HTTP/3 layer a stopgap until Node.js adds native support. A proxy that downgrades HTTP/3 removes the stream independence you adopted WebTransport for. WebTransport also has no built-in reconnection, and firewall traversal is unproven, because QUIC runs over UDP, which restrictive networks may block.

Choose if: you send frequent updates in which each message replaces the last, such as live cursors, and you are one of the few teams that can run HTTP/3 end to end today.

Long polling replaces the stream with a loop of ordinary HTTP requests: the server holds each request open until an event is ready, and the client sends the next request as soon as a response arrives. The client sends its own messages on separate requests, as with SSE plus POST.

Gains: each response ends as soon as an event is sent, so long polling gets through the buffering proxies that hold SSE events back. Reconnection needs little code, and each message has its own request and log line, which makes debugging easy.

Costs: the same ordering problem as SSE plus POST, request overhead that grows with your message rate, and added latency for events that arrive between one response and the next request.

Choose if: proxies on your clients' networks hold back long-lived responses. On other networks, SSE plus POST reaches the same clients with less overhead, which is why long polling now mostly runs underneath Socket.IO and managed platforms rather than on its own.

Copy link to clipboard

Should you adopt a managed realtime platform instead?

Each of the four alternatives leaves at least one gap: ordering, reconnection, or firewall traversal. A managed realtime platform closes those gaps by pairing a WebSocket connection with five capabilities:

  • Automatic fallback to an HTTP transport when a network blocks the WebSocket connection, which restores the firewall traversal SSE gave you.

  • Reconnection with recovery, so a reconnecting client receives the messages the client missed instead of rejoining the live stream with a gap.

  • Fan-out across servers, so one message reaches every subscriber, whichever server each subscriber is connected to. A user with your app open on a laptop and a phone sees the same state on both devices.

  • Connection multiplexing, so many logical channels share one connection, which removes the six-connection limit in the same way HTTP/2 does.

  • Delivery guarantees, such as ordering and deduplication, which SSE leaves to your application. The strength of each guarantee varies by vendor, so check each vendor's documentation for the exact limits.

Fallback and reconnection close gaps that a migration away from SSE opens. Fan-out, multiplexing, and delivery guarantees are additions, because SSE never provided any of the three, so buy a platform for those three only if your product needs them.

Fan-out is a backend capability rather than a transport capability. A self-hosted deployment gets fan-out from a pub/sub backplane, such as Redis, that relays each message between your connection servers. A backplane works the same way behind SSE servers and WebSocket servers. A managed platform runs the equivalent of a backplane for you.

Copy link to clipboard

Hosted or self-hosted realtime infrastructure

Whether hosted or self-hosted, a realtime platform needs servers that hold every client connection open and track which messages each client has received. Running those connection servers carries most of the operational weight.

A hosted platform runs the connection servers for you, and in exchange you take on a vendor dependency. Your availability depends on the vendor's availability, the vendor defines the protocol, and your costs follow the vendor's pricing. Self-hosting with a server such as Centrifugo or a Socket.IO cluster keeps availability, protocol, and cost under your control, and leaves your team to run and scale the servers.

The decision comes down to whether your team should spend engineering time running connection infrastructure. For the detail behind that decision, see what building realtime infrastructure costs and a feature comparison of self-hosted options.

Copy link to clipboard

When a managed platform earns its place

A managed platform is worth evaluating when at least one of these conditions holds:

  • The WebSocket trial shows failures concentrated in accounts you can't afford to lose, and you don't want to operate fallback transports and sticky sessions yourself.

  • One user's devices, or many users on one channel, must see the same state, and you don't want to operate a pub/sub backplane.

  • Reconnecting clients must receive every missed message. Socket.IO's connection state recovery is off by default, and among Socket.IO's Redis adapters, only the Redis Streams adapter supports recovery.

  • Your connection count or geographic spread makes running connection servers a project in its own right.

In each case, the alternative to a platform is building the capability and then operating the capability indefinitely.

A platform is the wrong choice when SSE plus a POST endpoint already covers your needs. Your clients may send only in response to user actions, ordering between the POST and the stream may not matter, and you may not need fan-out across devices. In that situation, a platform adds a recurring bill and a vendor dependency for capabilities you would not use.

Copy link to clipboard

Which SSE alternative should you choose?

Three questions decide which alternative fits.

  1. How often do your clients send? Each message on a POST endpoint is a separate HTTP request, with the headers and round trip that a request involves. When one user action produces one message, the overhead stays invisible, because people click slowly. When your client sends several messages a second without user action, one persistent connection costs less. If you don't know your clients' send rate, measure outbound messages per client at your busiest hour.

  2. Must client messages be ordered against server events? If the answer is yes, you need one connection that carries both client messages and server events. No alternative that sends client messages on a separate request can guarantee the ordering.

  3. Which accounts fail the WebSocket trial? If failures are rare and spread across accounts, you can move to WebSockets without a fallback. If failures concentrate in any account you can't afford to lose, you need a fallback, whatever the overall failure rate.

The table below turns your answers to the three questions into a recommendation. Find the row that matches your situation: each row names the alternative to choose, the reason, and the work you take on.

Your situation

Choose

Why

What you take on

Clients send in response to user actions, and client messages don't need to be ordered against the stream

Keep SSE and add a POST endpoint

The smallest change that adds a return path. Reconnection and firewall traversal stay as they are.

A stream marker if ordering matters later, and a message bus or sticky sessions if you run more than one server

Clients send without user action, or client messages must be ordered against server events, and the WebSocket trial shows no failures in accounts you depend on

WebSockets through Socket.IO, or SignalR on .NET

Both conditions need one ordered connection. The library supplies reconnection and an HTTP fallback.

Running and scaling connection servers, with sticky sessions if the fallback is on

The WebSocket trial shows failures in any account you can't afford to lose

WebSockets with automatic HTTP fallback, from a managed platform or a library you operate

The overall failure rate does not matter when the failures sit in accounts you can't afford to lose.

Two transports and sticky sessions if you build, or a vendor dependency if you buy

One user's state must stay consistent across devices, or many subscribers share one stream

A managed platform, or self-hosted connection servers with a pub/sub backplane

Fan-out needs a backplane whichever transport you use, including SSE.

A backplane to operate if you build, or a vendor dependency if you buy

Binary payloads are a large share of your traffic

WebSockets, self-hosted or managed

WebSockets carry binary data natively. SSE adds about 33% to every binary payload through Base64 encoding.

The same operational work as any WebSocket deployment

Copy link to clipboard

When SSE is still the right answer

SSE remains the right choice when traffic flows one way, from server to client. SSE plus a POST endpoint also covers two-way traffic, when clients send in response to user actions and ordering between the POST and the stream does not matter.

SSE is also the pragmatic choice when you deliver into networks you don't control, and want neither a hosted dependency nor a client-side library. The strongest argument for staying on SSE is that reconnection and firewall traversal already work, and every alternative except the POST endpoint requires you to rebuild reconnection, firewall traversal, or both.

Copy link to clipboard

How to migrate off SSE without losing clients

SSE and a replacement transport use different endpoints, so both can run side by side. If you are moving to WebSockets, use the WebSocket trial to find the accounts that can connect, then route a small group of clients from those accounts to the new transport. Compare that group's error rates and message loss against clients still on SSE.

Put the transport choice behind a flag you can switch per account, so you can move a failing account back to SSE without a release. Both transports can read from the same replay store, so running both at once adds little cost. Keep the SSE path until the replacement has run through a full release cycle, including a mobile app update, and until you have data from your enterprise accounts specifically.

Carry your heartbeats over to the new transport. Load balancers and proxies close connections that stay quiet: the AWS Application Load Balancer idle timeout defaults to 60 seconds, and the Cloudflare proxy read timeout defaults to 125 seconds. A new transport without heartbeats hits the same timeouts your SSE heartbeats were preventing.

Copy link to clipboard

Ably: a managed alternative to SSE

Ably is a managed realtime platform built on WebSockets. Ably provides all five platform capabilities described above, and this section states the limit of each capability in production.

Copy link to clipboard

Fallback that keeps the return path

Ably clients connect over WebSocket wherever the network allows. When a network blocks the WebSocket connection, the Ably SDK falls back to HTTP long polling, which stays two-way: the client publishes on HTTP requests and receives messages on long-poll responses. If a network blocks every transport, the SDK reports error 80001.

Copy link to clipboard

Reconnection with recovery, inside a two-minute window

Ably holds connection state for two minutes. A client that reconnects inside the two-minute window receives every message the client missed, in order, with no catch-up code. After two minutes, the channel reattaches with the resumed flag set to false, and your code fetches the missing messages with history({untilAttach: true}).

A mobile app in the background routinely stays disconnected for longer than two minutes, so Ably's recovery is bounded rather than guaranteed. Every provider that offers a recovery window has the same trade-off at some duration. For how the window and reattachment behave in practice, see connection recovery.

Copy link to clipboard

Ordering and exactly-once delivery

Messages from a publisher using an Ably realtime SDK reach subscribers in the order published, because each message on a realtime connection carries an incrementing serial number (message ordering). Idempotent publishing discards a duplicate publish with the same message ID. A client that reconnects inside the two-minute window resumes from the last serial number the client received. Together, idempotent publishing and resumption give exactly-once delivery for clients that reconnect within two minutes.

But it's worth mentioning that three limits apply to Ably's ordering:

  1. Messages published at a high rate on separate REST requests can arrive out of order

  2. Messages published almost simultaneously in different regions can appear in a different order in each region.

  3. A server recycled during connection recovery can replay missed messages out of order.

Copy link to clipboard

Fan-out and multiplexing

An Ably channel delivers each message to every subscriber, so one user's laptop and phone stay consistent. All channels share one connection, which removes the six-connection limit, as HTTP/2 would for SSE.

Ably's SSE endpoint is subscribe-only. Ably also offers an SSE endpoint for constrained environments and platforms without an Ably SDK. Clients can't publish over the SSE endpoint, so the endpoint does not solve the return-path problem. For two-way traffic, use the WebSocket transport and its HTTP fallback.

To see how fallback and recovery behave with your own clients, start with the getting started guide, or create an account.

Copy link to clipboard

What are the alternatives to Server-Sent Events for two-way communication?

Four alternatives give the client a return path. An HTTP POST endpoint beside your SSE stream suits clients that send in response to user actions. WebSockets, usually through Socket.IO or SignalR, suit clients that send continuously or need client messages ordered against server events. Long polling suits networks whose proxies hold back long-lived responses, and today mostly runs as a fallback. WebTransport is not yet deployable on most server stacks. Instead of building on any of the four, you can buy a managed platform, such as Ably, Pusher, or PubNub, that pairs WebSockets with fallback and recovery.

Copy link to clipboard

Can I keep SSE's automatic reconnection if I move to WebSockets?

Not from the browser's WebSocket API. Automatic retry and the Last-Event-ID header belong to the browser's EventSource API, and the WebSocket API has no equivalent. You get reconnection back from a library such as Socket.IO or SignalR, from a managed platform, or by writing a retry loop, a backoff strategy, and the code that decides which events a reconnecting client still needs.

Copy link to clipboard

Which transport works when corporate firewalls block the WebSocket upgrade?

Any transport that stays within ordinary HTTP works, because an ordinary HTTP request has no Upgrade step for a proxy to reject. For two-way traffic, SSE plus a POST endpoint reaches the same networks your SSE stream already reaches. Long polling also works, and also gets through proxies that hold back streamed responses. WebTransport is unlikely to help, because WebTransport runs on UDP, which restrictive networks may also block. If you want WebSockets anyway, you need automatic fallback to HTTP, from a library, a managed platform, or two transports you run yourself.

Copy link to clipboard

Is WebTransport ready to replace WebSockets in production?

Not for most teams, and the blocker is server infrastructure rather than browsers. Chrome, Firefox, and Safari all support WebTransport as of March 2026, but common proxies and server runtimes either lack WebTransport support or describe their support as provisional. A proxy that terminates HTTP/3 and forwards traffic over an older protocol removes the per-stream independence that justifies WebTransport. Revisit WebTransport when HTTP/3 can run from the browser to your application with no downgrade in between.

Copy link to clipboard

Should I use SSE plus HTTP POST instead of WebSockets?

Often, yes. When one user action produces one outbound message, SSE plus POST keeps SSE's reconnection and firewall traversal, adds a return path, and costs one endpoint. SSE plus POST stops being the right choice in two cases: when clients send often enough that per-request overhead matters, and when a client message must be ordered against the events the message triggers. Reconciling order across two channels on the client is more work than using one ordered connection.

Copy link to clipboard

What about gRPC, WebRTC, or ,,?

None of the three replaces SSE for the connection between your server and a browser. gRPC streams two-way over HTTP/2 between backend services, but browsers can't use native gRPC, and gRPC-Web needs a translating proxy and does not support client streaming. WebRTC data channels are two-way and traverse NATs well, but opening a data channel requires a separate signaling channel, which itself runs over WebSockets or SSE. Streamable HTTP, which the Model Context Protocol adopted in March 2025, is a version of SSE plus POST for traffic between AI applications and MCP servers.

Copy link to clipboard

How many SSE connections can a browser hold open at once?

Six per host name over HTTP/1.1. All tabs open on your site share those six connections with every other request to the same host, so each open SSE stream takes one connection away from API calls and page assets. The limit is a browser convention rather than part of the SSE protocol, and Chromium closed a request to raise the limit as "Won't fix", pointing to HTTP/2 as the fix instead. Over HTTP/2, all streams to a host share one connection, and the six-connection limit no longer applies.

Copy link to clipboard

Does SSE work over HTTP/2?

Yes. Over HTTP/2, every SSE stream to a host shares one TCP connection, and the ceiling becomes the server's SETTINGS_MAX_CONCURRENT_STREAMS value, commonly 100 or 128. HTTP/2 changes nothing else about SSE: streams remain one-way, text-only, and GET-only, so HTTP/2 does not affect the return-path decision.

Join the Ably newsletter today

1000s of industry pioneers trust Ably for monthly insights on the realtime data economy.
Enter your email