Channels

Channels are used to separate messages into different topics. They provide a way to implement the Publish-Subscribe (Pub/Sub) architectural pattern. Pub/Sub enables any number of publishers to publish messages to a channel, and any number of subscribers to be subscribed to a channel to receive them, with publishers and subscribers completely decoupled from one another.

Clients can subscribe to channels and publish messages using the realtime interface of an Ably SDK. Clients can only publish messages when using the REST interface.

Channel namespaces enable channels to be grouped together based on a prefix that is included as part of the channel name. A colon : is used to delimit a channel namespace, and a namespace is the first segment of a channel name up until the first colon. If a channel name does not contain a colon, the namespace is the entire channel name.

The following are examples of channels that are all part of the ‘customer’ namespace:

  • customer
  • customer:tracking-id
  • customer:order:update

Namespaces can be used to apply operations to all channels within the namespace, such as capabilities, channel rules and integration rules. Namespaces are not required to refer to a set of channels within a capability. A resource specifier, such as foo:*, a glob expression, will match a channel named foo:bar, even without a foo namespace.

A Channel object is a reference to a single channel and is uniquely identified by its unicode string name. A channel is created, or an existing channel is retrieved from the Channels collection, using the get() method. You can only connect to one channel in a single operation. Wildcards are not supported.

Although Ably recommends that you use channels to distribute work more evenly across the cluster, there is an associated cost for a high number of channels. Don’t use different channels just to indicate different types of data, or different events, if all messages are going to the same set of clients. Use a single channel and distinguish between them using a different message name.

Channels are the unit of security and scalability. If you are sending data that must not be shared with certain clients, ensure it is on a channel that those clients don’t have the capabilities to attach to.

The following is an example of creating a channel:

Select...
const channel = realtime.channels.get('channelName');
Copied!

Attaching to a channel ensures that it is created in the Ably system and that all messages published on the channel are received by any channel listeners registered by calling subscribe(). Ably will start to stream messages to a client as soon as they have attached, regardless of whether or not they have yet subscribed. Attach is only available to the realtime interface.

Channels are not pre-configured or provisioned by Ably in advance. They are created on demand when clients attach, and remain active until such time that there are no remaining attached clients.

Note that attach() can be called explicitly, however it’s more common for a client to subscribe, which will automatically initiate the attach.

The following example explicitly attaches to a channel, which results in the channel being provisioned in Ably’s global realtime cluster. This channel will remain available globally until there are no more clients attached to the channel:

Select...
const channel = realtime.channels.get('chatroom'); await channel.attach();
Copied!

A client can detach from a channel so that it no longer receives any messages published to that channel. Detaching is different to unsubscribing from a channel because unsubscribe() is a client-side operation. The Ably platform does not know that a client has unsubscribed and will continue to stream messages to that client until detach() is called.

A channel will automatically close when all of the following criteria are met:

  • There are no more realtime clients attached to it
  • Approximately one minute has passed since the last client detached
  • Approximately one minute has passed since the last message was published to the channel

The following is an example of detaching from a channel:

Select...
const channel = realtime.channels.get('chatroom'); await channel.detach();
Copied!

Use the publish() method to send messages to a channel. All clients that are subscribed to that channel will receive the messages. Publishing messages is an operation available to the REST and realtime interfaces.

The following is an example of publishing a message to a channel:

Select...
const realtime = new Ably.Realtime.Promise('<loading API key, please wait>'); const channel = realtime.channels.get('guy-jaw-jam'); await channel.publish('example', 'message data');
Demo Only
Copied!

To publish a single message to multiple channels, make multiple publish() requests using the realtime interface. These concurrent requests can be in-flight simultaneously, ensuring that a publish on one channel does not delay operations in other channels. To publish to multiple channels use the batch publish feature.

It is possible to publish messages to multiple channels with a single request. A batch request queries an API multiple times with single HTTP request. A batch request has a single set of request details containing the request body, parameters and headers. These are converted into an array of requests to the underlying API. Each individual request to the underlying API is performed in parallel and may succeed or fail independently.

The following is an example of a batch publish request using the request() method to query the batch REST API

Select...
const ablyRest = new Ably.Rest.Promise({ key: '<loading API key, please wait>' }) const content = { 'channels': [ 'test1', 'test2' ], 'messages': { 'data': 'myData' } } const batchPublish = await ablyRest.request('post', '/messages', null, content, null); console.log('Success! status code was ' + batchPublish.statusCode)
Demo Only
Copied!

Each batch publish request can contain a single BatchSpec object, or an array of BatchSpec objects. Each BatchSpec object contains a single channel name or an array of channel names in the channels property. The messages property then contains a single message or an array of messages. Each BatchSpec will then publish each of its messages to each of its channels.

For each channel, the messages grouped into a single BatchSpec are published atomically. This means that:

  • Either they will all be successfully published or none of them will
  • The max message size limit applies to the total size of all messages in in a BatchSpec
  • Each BatchSpec will only count as a single message for the purpose of the per-channel rate limit

So if you do not need the atomicity guarantee and might be in danger of exceeding the max message size limit, you can put each message into its own BatchSpec (relative ordering will still be preserved). Conversely, if you are publishing many hundreds of small messages and are in danger of exceeding the max per-channel message rate, you group them into a fewer BatchSpecs.

The batch request as a whole is subject to the following limits:

  • Each request can only include 100 different channels. If the same channel name appears in multiple BatchSpec objects within a single request, it only counts as one channel towards the 100 channel limit per batch request.
  • Each request has a maximum body size of 2MiB.

The following is an example of a single BatchSpec object publishing a single message to 2 channels:

{ channels: ['channel1', 'channel2'], messages: {data: 'My message contents'} }
Copied!

The following is an example of an array of BatchSpec objects. The first publishes a single message to two channels and the second publishes two messages to a single channel:

[ { channels: ['channel1', 'channel2'], messages: {data: 'My message contents'} }, { channels: 'channel3', messages: [ {data: 'My message contents'}, {name: 'an event', data: 'My event message contents'}, ] } ]
Copied!

The following is an example curl request, querying the REST API directly:

curl -X POST https://rest.ably.io/messages \ -u "<loading API key, please wait>" \ -H "Content-Type: application/json" \ --data '{ "channels": [ "test1", "test2"], "messages": {"data": "My test message text" } }'
Demo Only
Copied!

Once all requests have been completed in a batch request, a batch response is returned with three possible outcomes:

Success
If all of the individual requests were successful then an array containing the response of each query is returned in request order.
Failure
If the batch request itself failed before the individual requests were made, then an error response is returned with a status code and error response body. Examples of why the batch request can fail include an authorization failure or an invalid request.
Partial success
If one or more of the individual requests failed the response body contains an error object with the error code 40020 and a status code of 400. The error body contains a batchResponse array of each individual response in request order. The batchResponse can be inspected if there is a need to know the details of each outcome. If you only need to know whether or not the batch request was completely successful then the status code is sufficient.

The examples for each possible outcome will use the following BatchSpec object as the request data:

{ channels: ['channel0', 'channel1', 'channel2'], messages: {data: 'My test message text'} }
Copied!

The following is an example of a successful batch publish response. The response body contains the messageId of each published message and the channel it was published to. The status code is 201:

[ { "channel":"channel0", "messageId":"w234r5t-fr5" }, { "channel":"channel1", "messageId":"vde4sfc0p" }, { "channel":"channel2", "messageId":"nh3exv8ih" } ]
Copied!

The following is an example of a batch publish failure response. The response body contains the details of the error, in this example that the token used for the request has expired. The status code is 401:

{ "error": { "message":"Token expired", "statusCode":401, "code":40140 } }
Copied!

The following is an example of a batch publish partial success response. The successful requests contain the messageId of each published message and the channel they were published to. The failed request contains the channel the request failed for and the details of the error, in this example that the credentials used didn’t have the capability to publish to that channel. The status code for a partial success is always 400:

{ "error": { "message": "Batched response includes errors", "statusCode":400, "code":40020 } "batchResponse": [ { "channel":"channel0", "messageId":"w234r5t-fr5" }, { "channel":"channel1", "messageId":"vde4sfc0p" }, { "channel":"channel2", "error": { "message": "Given credentials do not have the required capability", "statusCode": 401, "code": 40160 } } ] }
Copied!

Transient publishing is when a client publishes messages without attaching to a channel. This is a feature of the realtime interface of certain Ably SDKs. Transient publishing can be beneficial if you intend to publish to many channels as it removes the need to attach to a channel each time you publish. It also avoids a client subscribing to messages which avoids messages being sent to it redundantly.

The following is an example of publishing without attaching to a channel:

Select...
const channel = realtime.channels.get('chatroom'); // The publish below will not attach you to the channel await channel.publish('action', 'boom!');
Copied!

Idempotency ensures that multiple publishes of the same message cannot result in duplicate messages.

It is possible that a client publishing a message using the REST interface may not receive acknowledgement of receipt from Ably, due to issues such as network failure outside of Ably’s control. Clients will automatically attempt to re-publish messages in these instances, which could result in duplicate messages.

If idempotent publishing is enabled using the idempotentRestPublishing ClientOptions, the Ably SDK will assign a unique ID to each message which ensures that subsequent retry attempts cannot result in duplicate messages.

It is also possible to manually specify message IDs. The following is an example of how you might do this:

Select...
const rest = new Ably.Rest.Promise('<loading API key, please wait>'); const channel = rest.channels.get('guy-jaw-jam'); await channel.publish([{data: 'payload', id: 'unique123'}]);
Demo Only
Copied!

If manually specifying message IDs, it is important to be aware of how messages are published when calling the publish() method with an array of messages. See this FAQ for further information.

You can use the REST interface of an Ably SDK to publish messages on behalf of a realtime connection.

To publish on behalf of a realtime connection, the REST publisher requires the connectionKey of the realtime client. The connectionKey is a secret of the client unless explicitly shared. The REST publisher can then set the connectionKey in the root of the published message.

If the realtime connection is identified by being bound to a clientId, then the REST publish must include that same clientId. This can be included in the message itself to apply to only that message, in the case that the REST client is able to assume any clientId, or using a REST client bound to that specific clientId.

The publish attempt will fail in the following scenarios:

  • the connectionKey is invalid
  • the connectionKey belongs to a connection that has since been closed
  • the REST publisher is using a different Ably application to the realtime client
  • the clientIds don’t match between the realtime connection and the REST publish

Subscribe to a channel in order to receive messages being published to it, by registering a listener. Subscribing is an operation available to the realtime interface and uses the subscribe() method.

Subscribing to events server-side using the Pub/Sub method can be disadvantageous as it can increase latency or duplicate events among multiple servers. Message Queues are more a appropriate method to use in that instance, as multiple worker servers enable Ably to distribute the load of messages received from published. This means that each message is only processed once by any one of your worker servers.

A client can subscribe to all messages published to a channel by passing a listener function to the subscribe() method. The listener is passed a Message object for each message received. Alternatively, a client can listen for a subset of messages based on the name of the published message.

The following is an example of registering a listener for all messages:

Select...
const realtime = new Ably.Realtime.Promise('<loading API key, please wait>'); const channel = realtime.channels.get('guy-jaw-jam'); await channel.subscribe((message) => { alert('Received: ' + message.data); });
Demo Only
Copied!

The following is an example of registering a listener for a specific message name:

Select...
await channel.subscribe('myEvent', (message) => { console.log('message received for event ' + message.name); console.log('message data:' + message.data); });
Copied!

Although the attach operation can be initiated explicitly by a client, it is more common for the client to simply subscribe, which will automatically initiate the attach, if the channel is not already attached.

Normally, errors in attaching to a channel are communicated through the attach() callback. For implicit attaches there is no callback, so if you want to know what happens, you’ll need to listen for channel state changes. This is also true in other cases where a channel is attached or re-attached automatically, for example, following the library reconnecting after a period in the suspended state.

The following is an example of implicitly attaching to a channel and publishing a message:

Select...
const channel = realtime.channels.get('chatroom'); await channel.subscribe('action', (message) => { // implicit attach console.log('Message received ' + message.data); }); await channel.publish('action', 'boom!');
Copied!

Unsubscribing from a channel removes previously registered listeners that were added when subscribing to it.

The following is an example of removing listeners registered for a single event and an example of removing listeners registered for all events:

Select...
/* remove the listener registered for a single event */ channel.unsubscribe('myEvent', myListener); /* remove the listener registered for all events */ channel.unsubscribe(myListener);
Copied!

Subscription filters enable you to subscribe to a channel and only receive messages that satisfy a filter expression.

Messages are immediately streamed to clients as soon as they attach if they have subscribe capabilities for that channel. Subscription filters apply server-side filtering to messages, meaning that a client will only ever receive the messages that they subscribe to.

Subscription filters are currently in preview status.

Filter expressions should be written using JMESPath. They can be constructed using the message name and message.extras.headers fields.

message.extras.headers optionally provides ancillary metadata to a message, as Ably can’t inspect message payloads themselves. Adding suitable key-value pairs to messages will enable more complicated filter expressions to be constructed resulting in more effective message filtering.

The following is an example of publishing a message with additional metadata:

Select...
const channel = realtime.channels.get('scoops-kiosk'); await channel.publish({ name: 'ice-cream', data: '...', extras: { headers: { flavor: 'strawberry', cost: 35, temp: 3 } } });
Copied!

Be aware that message.extras.headers must be a flat object. It can’t contain any further nesting or arrays.

The following is an example of a filter expression subscribing to messages with the name “ice-cream”, a flavor of “strawberry” and a cost of less than 50:

name == `"ice-cream"` && headers.flavor == `"strawberry"` && headers.cost < `50`
Copied!

The following is an example of a filter expression subscribing to messages with a flavor of either “strawberry” or “chocolate”:

headers.flavor == `"strawberry"` || headers.flavor == `"chocolate"`
Copied!

In order to subscribe to a channel with a filter expression, you obtain a channel instance using the getDerived() method. This accepts a filter expression as a parameter.

The following is an example of subscribing to a channel using one of the previous example filters:

Select...
const channel = realtime.channels.getDerived('scoops-kiosk', { filter: 'name == `"ice-cream"` && headers.flavor == `"strawberry"` && headers.cost < `50`' }) await channel.subscribe(...);
Copied!

The following example demonstrates publishing to a channel, but subscribing to only a subset of messages on it:

Select...
// Connect to Ably const realtime = new Ably.Realtime({'<loading API key, please wait>'}); // Create a channel instance to publish to const pubChannel = realtime.channels.get('scoops-kiosk'); // Create a channel instance using the filter qualifier const subChannel = realtime.channels.getDerived('scoops-kiosk', { filter: 'name == `"ice-cream"` && headers.flavor == `"strawberry"` && headers.cost < `50`' }); // Subscribe to the channel using the filtered subscription await subChannel.subscribe((message) => { alert('Ice cream update: ' + message.data); }); // Publish to the unfiltered channel instance await pubChannel.publish({ name: 'ice-cream', data: '...', extras: { headers: { flavor: 'strawberry', cost: 35, temp: 3 } }); });
Demo Only
Copied!

Clients require the subscribe capability for one of the following resources in order to receive messages from a subscription filter:

  • [filter]<channel name>
  • [*]<channel name>
  • [*]*

A client may also attach to the unfiltered instance of a channel for other operations, such as to subscribe to the presence set. Be aware that if clients attach to the unfiltered instance, and have the subscribe capability for the channel itself, they will be sent all messages by Ably. This is because of the difference between attaching and subscribing to a channel.

The following features are not supported using subscription filters:

Channel options can be used to customize the functionality of channels. This includes enabling features such as encryption and deltas, or for a client to retrieve messages published prior to it attaching to a channel using rewind.

Metadata provides additional information about apps or channels. It includes uses such as enabling clients to be aware of how many other clients are attached to a channel without the need to use the presence feature. Examples of channel metadata available include the status and occupancy of specific channels.

Channel rules can be used to enforce settings for specific channels, or channel namespaces. They can be broadly categorized into three different types:

  • For message storage
  • For client security and identification
  • To enable features for a channel or namespace

The channel rules related to message storage are:

Persist last message
if enabled, the very last message published on a channel will be stored for a year. This message is retrievable using rewind by attaching to the channel with rewind=1. If you send multiple messages in a single protocol message, for example calling publish() with an array of messages, you would receive all of them as one message. Be aware that presence messages are not stored and that messages stored in this manner are not accessible using history. Note that for each message stored using this rule, an additional message is deducted from your monthly allocation.
Persist all messages
if enabled, all messages published on a channel will be stored according to the storage rules for your account. This is 24 hours for free accounts and 72 hours for paid accounts. Messages stored in this manner are accessible using history. Note that for each message stored using this rule, an additional message is deducted from your monthly allocation.

The channel rules related to security and client identity are:

Identified
if enabled, clients will not be permitted to use (including to attach, publish, or subscribe) matching channels unless they are identified (they have an assigned client ID). Anonymous clients are not permitted to join these channels. Find out more about authenticated and identified clients.
TLS only
if enabled, only clients who have connected to Ably over TLS will be allowed to use matching channels. By default all of Ably’s client libraries use TLS when communicating with Ably over REST or when using our Realtime transports such as Websockets.

The channel rules related to enabling features are:

Push notifications enabled
If checked, publishing messages with a push payload in the extras field is permitted. This triggers the delivery of a Push Notification to devices registered for push on the channel.
Message interactions enabled
If enabled, messages received on a channel will contain a unique timeserial that can be referenced by later messages for use with message interactions.

To set a channel rule in the Ably dashboard:

  1. Sign in to your Ably account.
  2. Select an app.
  3. Go to Settings tab.
  4. Click Add new rule.
  5. Select channel name or namespace to apply rules to.
  6. Check required rules.

Channel history enables clients to retrieve messages that have been previously published on the channel. Messages can be retrieved from history for up to 72 hours in the past, depending on the persistence configured for the channel.

The presence feature enables clients to be aware of other clients that are ‘present’ on the channel. Client status is updated as they enter or leave the presence set. Clients can also provide an optional payload describing their status or attributes, and trigger an update event at any time.

A channel can exist in any of the following states:

initialized
The Channel has been initialized, but no attach has been attempted yet.
attaching
An attach has been initiated by sending a request to Ably. This is a transient state and will be followed either by a transition to attached, suspended, or failed.
attached
An attach has succeeded. In the attached state a client can publish and subscribe to messages, and enter the presence set.
detaching
A detach has been initiated on the attached Channel by sending a request to Ably. This is a transient state and will be followed either by a transition to detached or failed.
detached
The Channel, having previously been attached, has been detached by the client.
suspended
The Channel, having previously been attached, has lost continuity. This is normally due to the client being disconnected from Ably for more than two minutes. The client will automatically attempt to reattach as soon as connectivity is restored.
failed
An indefinite failure condition. This state is entered if a Channel error has been received from the Ably service (such as an attempt to attach without the necessary access rights).

Connection state also impacts the state of a channel in the following ways:

  • If the connection state becomes CLOSED, all channels will become DETACHED
  • If the connection state becomes FAILED, all channels will become FAILED
  • If the connection state becomes SUSPENDED, all previously-ATTACHED or ATTACHING channels will become SUSPENDED
  • If the connection state becomes CONNECTED, any channels that were SUSPENDED will be automatically reattached

The Channel object is an EventEmitter. Events are emitted with a name that corresponds to the new channel state, whenever there is a channel state change. Register a channel state change listener with the on() or once() methods, depending on whether you want to monitor all channel state changes, or only the first occurrence of one.

Remove channel state listeners with the off() method.

Listeners are passed a ChannelStateChange object in the first argument. This object has the following properties:

  • current / previous: the present and last state of the channel.
  • resumed: a flag indicating whether message continuity on the channel is preserved since the last time the channel was attached.
  • reason: the reason for the state change, if available.

As with all events from an EventEmitter in the Ably library, this within the listener function is a reference to an event object whose event property is the name of the event that fired. This allows a listener to listen for all events with a single registration and still know which type of event is fired.

The Channel object can also emit one event that is not a state change: an update event. This happens when there’s a change to channel conditions for which the channel state doesn’t change. For example, a partial loss of message continuity on a channel (typically after a resume) for which the channel state remains attached would lead to an update event being emitted, with both current and previous set to attached and the resumed flag set to false. So if you get such an event, you’ll know there may be messages you’ve missed on the channel, and if necessary you can use history to retrieve them.

Select...
channel.on('attached', (stateChange) => { console.log('channel ' + channel.name + ' is now attached'); console.log('Message continuity on this channel ' + \ (stateChange.resumed ? 'was' : 'was not') + ' preserved'); });
Copied!

Alternatively, a listener may be registered so that it receives all state change events.

Select...
const myListener = (stateChange) => { console.log('channel state is ' + stateChange.current); console.log('previous state was ' + stateChange.previous); if (stateChange.reason) { console.log('the reason for the state change was: ' + stateChange.reason.toString()); } }); channel.on(myListener);
Copied!

Previously registered listeners can be removed individually or all together.

Select...
/* remove the listener registered for a single event */ channel.off('attached', myListener); /* remove the listener registered for all events */ channel.off(myListener);
Copied!

Be aware that when registering listeners for channel state changes, certain repeating states may add new listeners each time.

Channel attach and detach operations are asynchronous. After initiating an attach request, the client will wait for a response from Ably that confirms that the channel is established on the service and then trigger a state change event.

Ably SDKs will attempt to automatically recover from non-fatal error conditions. However, you can handle them yourself if you prefer by subscribing to channel state changes, or using the callbacks available when explicitly calling attach().

Select...
const channel = realtime.channels.get('private:chatroom'); channel.on('failed', (stateChange) => { console.log('Channel failed, reason: ', stateChange.reason); }); await channel.attach();
Copied!

Some classes of errors are fatal. These cause the channel to move to the FAILED state. Ably SDKs will not attempt any automatic recovery actions. For example, when attempting to attach to a channel, with a token that doesn’t have the subscribe capability for that channel, will cause that channel to enter the FAILED state.

Whilst fatal errors won’t get better on their own, they are fixable. For example, if a channel goes into the FAILED state due to the client not having the right capabilities to attach to it, that client could call authorize() to obtain a new token which does have the right capabilities, then call attach() on the channel. The library will not automatically reattach in the FAILED state, however explicit calls to attach() will make the client try again.

Some types of errors are non-fatal. For example, a client may have network connectivity issues, or a channel may experience a loss of strict message continuity. Ably SDKs will automatically attempt to recover from these events. If channel continuity is lost in the process, the library will notify you through a resumed flag in the ATTACHED or UPDATE event, so that you can decide how to handle the failure.

For every channel ATTACHED and UPDATE event, the ChannelStateChange object contains a resumed attribute. When true, there has been no loss of continuity from the last time the channel was attached. When false, there has been a loss of continuity.

For example:

  • The first time a client attaches to a channel on a fresh connection, resumed will be false, as there was nothing to continue from.
  • If a client successfully recovers a connection and reattaches to its channels, the resumed flag on the ATTACHED events will tell it whether message continuity was preserved, or not. Any channel for which it’s true, is guaranteed to receive every message it missed while the client was disconnected.
  • If a client resumes or recovers a connection unsuccessfully continuity is lost and the client receives a fresh connection. This generally happens because the client was disconnected for more than two minutes, which is how long Ably holds connection state for. If the client were resuming, all the channels (which will have gone into the SUSPENDED state after two minutes) will still reattach automatically, and the client will receive ATTACHED events with resumed set to false.
  • If Ably needs to signal a loss of message continuity on an attached channel, the client will receive an UPDATE event with resumed set to false. This occurs in situations such as a partially successful resume, where the client was disconnected for less than two minutes.
Channel namespaces
v1.2