The Ably SSE (Server-Sent Events) API provides real-time event streams without needing a full SDK or an MQTT library. SSE is a lightweight streaming layer over HTTP, primarily accessed through the EventSource API in modern web browsers — the preferred method to harness SSE.

With HTTP streaming, servers can maintain client requests and transmit data without repetitive requests, offering efficiency akin to WebSockets.

SSE allows subscribe-only functionality. This means you can’t:

  • Publish
  • Enter presence
  • Query the existing presence set
  • Attach and detach from channels without restarting the stream.

Ably advise SSE for simplified, subscribe-only streams on platforms. Its status as an open standard eliminates the need for client-side SDKs. However, the Ably SDK is recommended overall for its expanded features and superior reliability.

SSE is an excellent alternative to Ably SDK in memory-limited environments.

  • When operating under severe memory constraints
  • Where no Ably native library is available for your desired platform
  • When Ably provides only a REST SDK for your designated platform, but a realtime client interface is requisite
  • Where the sole operation is the subscription to channel events

The following code sample provides an example of how to use SSE with Ably:

var apiKey ='<loading API key, please wait>'; var url ='' + apiKey; var eventSource = new EventSource(url); eventSource.onmessage = function(event) { var message = JSON.parse(; console.log('Message: ' + + ' - ' +; };
Demo Only

Authentication with SSE allows for two methods: basic auth using an API key or token auth, using a token from your server. For enhanced security and control over connections, token auth is recommended for client-side use. Basic auth is simpler as it omits the need for an auth server and the potential need for clients to refresh expired tokens.

It is possible to use either basic auth, with an API key, or token auth, with a token issued from your server, with SSE. It’s recommended to use token auth on the client side for security reasons, so you have control over who can connect. Basic auth, while lacking this control, is simpler (it doesn’t require you to run an auth server), and you don’t have to worry about the client obtaining a new token when the old one expires.

If using basic auth, you can use a querystring parameter of key or an Authorization: Basic <base64-encoded key> header. If using token auth, you can use an accessToken querystring parameter or an Authorization: Bearer <base64-encoded token> header. See REST API authentication for more information.

Note that connection state is only retained for two minutes.

The SSE protocol and EventSource API seamlessly resume dropped connections. The client reconnects, supplying a lastEventId parameter, ensuring no event is missed from the previous connection’s endpoint. Ably uses this feature to resume channels from where they left off.

At the point of token expiration, the connection terminates. The default EventSource reconnection won’t function due to the expired credentials embedded in the connection URL. The solution is initiating a new connection with an updated accessToken, ensuring continuity by providing the right lastEventId for a seamless transition from the previous connection’s endpoint.

To enable transparent connection resumption when tokens must be renewed:

  1. Detect token expiration.
  2. Resume the connection precisely from the last delivered message using the lastEventId attribute.

If there is an error that causes a connection to be interrupted, the error event will be activated on the`EventSource instance. This applies to all types of connection disruptions. The data attribute of the event provides an Ably error body that describes the cause of the error. If the issue is related to the authorization token, the error code will indicate this. Token-related errors are identified by codes in the range of 40140 <= code < 40150. In these situations, a new accessToken can be obtained and authentication can be attempted again.

When you receive a message on a connection, it will include a lastEventId attribute with the last ID. To set this value for a new connection, specify it as a lastEvent parameter in the URL.

The following is an example of implementing message continuity with token auth:

let lastEvent; const connectToAbly = () => { // obtain a token const token = <GET-NEW-ABLY-AUTH-TOKEN> // establish a connection with that token const lastEventParam = lastEvent ? ('&lastEvent=' + lastEvent) : ''; eventSource = new EventSource(`${token}&channels=${channel}${lastEventParam}`); // handle incoming messages eventSource.onmessage = msg => { lastEvent = msg.lastEventId; // ... normal message processing } // handle connection errors eventSource.onerror = msg => { const err = JSON.parse(; const isTokenErr = err.code >= 40140 && err.code < 40150; if(isTokenErr) { eventSource.close(); connectToAbly(); } else { // ... handle other types of error -- for example, retry on 5xxxx, close on 4xxxx } } } connectToAbly();

The EventSource API will automatically attempt to reconnect and re-subscribe to the SSE endpoint in case of errors, even if the token has expired.

Manually re-subscribing to the SSE endpoint with a fresh token inadvertently creates two active subscriptions:

  1. The expired token that will consistently error out
  2. The new token

To avoid this, close the previous EventSource subscription with eventSource.close() before starting a new one, as shown in the code snippet.

In an SSE connection you can specify channel options in two different ways:

  1. With a query string in the channel name qualifier
  2. As a query string in the connection URL

By including options in the connection URL, they will apply to all attached channels. However, if you use a channel name qualifier, you can apply options to individual channels. This is useful if you need to override the options set in the connection URL for specific channels.

When creating a channel, you can use a qualifier in the form of square brackets at the beginning of the channel name. For example, to indicate the channel option with the name foo with value bar on a channel named baz the qualified channel name would be [?foo=bar]baz. If the channel name already has a qualifier, like [meta]log, you can add a query string after the existing qualifier, such as [meta?foo=bar]log.

The rewind and delta channel options are supported with SSE.

If you subscribe to a channel in delta mode using SSE, you must decode any delta messages you receive.

Certain transports may only provide the content of the data attribute of a message, without any accompanying metadata. This means that the receiver of the message may not have access to the extras or encoding attributes typically used to decode message updates.

To help applications utilizing these transports, `vcdiff` decoder libraries can examine the message payload’s start for the vcdiff header. This is an approximate method for determining whether the message is a standard message or a delta. It’s important to understand that, to depend on this check, you must ensure that the header is not present in any valid (uncompressed) message in your application. JSON messages, for instance, do not match the vcdiff header check, making it secure to conduct this sniffing on JSON message payloads.

For more information, see Deltas.

You can subscribe to messages in delta mode, using the SSE transport, as follows.

/* Make sure to include <script src=""></script> in your head */ var key = '<loading API key, please wait>'; var channel = 'oat-day-law'; var baseUrl = ''; var urlParams = `?channels=${channel}&v=1.1&key=${key}&delta=vcdiff`; var url = baseUrl + urlParams; var eventSource = new EventSource(url); var channelDecoder = new DeltaCodec.CheckedVcdiffDecoder(); eventSource.onmessage = function(event) { /* is JSON-encoded Ably Message (see */ var message = JSON.parse(; var { id, extras } = message; var { data } = message; try { if (extras && { data = channelDecoder.applyBase64Delta(data, id,; } else { channelDecoder.setBase(data, id); } } catch(e) { /* Delta decoder error */ console.log(e); } /* Process decoded data */ console.log(data); };
Demo Only

For more information on enveloped and unenveloped SSE, please see the SSE API

/* Make sure to include <script src=""></script> in your head */ var DeltaCodec = require('@ably/delta-codec'); var key = '<loading API key, please wait>'; var channel = 'sample-app-sse'; var baseUrl = ''; var urlParams = `?channels=${channel}&v=1.1&key=${key}&delta=vcdiff&enveloped=false`; var url = baseUrl + urlParams; var eventSource = new EventSource(url); var channelDecoder = new DeltaCodec.VcdiffDecoder(); eventSource.onmessage = function(event) { var data =; try { if (DeltaCodec.VcdiffDecoder.isBase64Delta(data)) { data = channelDecoder.applyBase64Delta(data).asUtf8String(); } else { channelDecoder.setBase(data); } } catch(e) { /* Delta decoder error */ console.log(e); } /* Process decoded data */ console.log(data); };
Demo Only

You can use the rewind channel option to choose the starting point of an attachment, either by specifying a specific moment in the past or a certain number of messages. For example, apply the rewind channel option with a value of 1 to all channels using a querystring parameter.

var querystring = 'v=1.2&channels=oat-day-law&rewind=1&key=<loading API key, please wait>'; var eventSource = new EventSource('' + querystring);
Demo Only

Or to specify the same parameter but only applying to one channel of two, using a qualified channel name:

var channelOne = encodeURIComponent('[?rewind=1]channel1'); var channelTwo = 'channel2'; var channels = channelOne + ',' + channelTwo; var querystring = 'v=1.2&key=<loading API key, please wait>&channels=' + channels'; var eventSource = new EventSource('' + querystring);
Demo Only

You can stream app statistics directly to the console using SSE by connecting and subscribing to the metachannel [meta]stats:minute.

The following is an example of subscribing to [meta]stats:minute:

curl -s -u "<loading API key, please wait>" "[meta]stats:minute&v=1.2"
Demo Only

The following is an example statistics event returned to the console from [meta]stats:minute:

id: 1083hjuJAB3NbG@1633679346115-0 event: message data: {"id":"MVphZHA7l9:0:0","timestamp":1633679346026,"encoding":"json","channel":"[meta]stats:minute","data":"{\<a href="\">intervalId\</a>"2021-10-08:07:48\",\<a href="\">unit\</a>"minute\",\<a href="\">schema\</a>"\",\"entries\":{\"messages.all.all.count\":1,\"messages.all.messages.count\":1,\"messages.outbound.realtime.all.count\":1,\"messages.outbound.realtime.messages.count\":1,\"messages.outbound.all.all.count\":1,\"messages.outbound.all.messages.count\":1,\"connections.all.peak\":2,\"connections.all.min\":1,\"connections.all.mean\":1,\"connections.all.opened\":1}}","name":"update"}

There may be a delay of up to one minute before receiving the initial statistics event. Use the rewind channel option to retrieve the most recent event and subscribe to subsequent events.

The following is an example curl command subscribing to [meta]stats:minute with a rewind value of 1:

curl -s -u "<loading API key, please wait>" "[meta]stats:minute&v=1.2&rewind=1"
Demo Only
When to use the SSE adapter