Realtime Client Library API

Channels

Ably Platform service organizes the message traffic within applications into named channels. Channels are the medium through which messages are distributed; clients attach to channels to subscribe to messages, and every message published to a unique channel is broadcast by Ably to all subscribers. This scalable and resilient messaging pattern is commonly called pub/sub. Connections are used to establish and maintain realtime communication with channels but are separate from channels, see the connection documentation for further information.

Getting started

The Ably Realtime client library provides a straightforward API for publishing and subscribing to messages on a channel. If the channel does not exist at the time the client is attached, a channel will be created in the Ably system immediately.

var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('den-oak-old');
channel.subscribe(function(message) {
  alert('Received: ' + message.data);
});
channel.publish('example', 'message data');
var Ably = require('ably');
var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('den-oak-old');
channel.subscribe(function(message) {
  console.log("Received: " + message.data);
});
channel.publish("example", "message data");
realtime = Ably::Realtime.new('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ')
channel = realtime.channels.get('den-oak-old')
channel.subscribe do |message|
  puts "Received: #{message.data}"
end
channel.publish 'example', 'message data'
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
Channel channel = realtime.channels.get("den-oak-old");
channel.subscribe(new MessageListener() {
  @Override
  public void onMessage(Message message) {
    System.out.println("New messages arrived. " + message.name);
  }
});
channel.publish("example", "message data");
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
IRealtimeChannel channel = realtime.Channels.Get("den-oak-old");
channel.Subscribe(message => {
  Console.WriteLine($"Message: {message.Name}:{message.Data} received");
});
channel.Publish("example", "message data");
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ"];
ARTRealtimeChannel *channel = [realtime.channels get:@"den-oak-old"];
[channel subscribe:^(ARTMessage *message) {
    NSLog(@"Received: %@", message.data);
}];
[channel publish:@"example" data:@"message data"];
let realtime = ARTRealtime(key: "xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ")
let channel = realtime.channels.get("den-oak-old")
channel.subscribe { message in
    print("Received: \(message.data)")
}
channel.publish("example", data: "message data")
final realtime = ably.Realtime(key: 'xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
final channel = realtime.channels.get('den-oak-old');
final channelMessageSubscription = channel
  .subscribe()
  .listen((ably.Message message) {
    print('Received: ${message.data}');
  }
);
await channel.publish(name: 'example', data: 'message data');

If you would prefer to just dive into code and see some examples of how to use channels, then we recommend you take a look at our Realtime tutorials.

Channels

In order to publish, subscribe to, or be present on a channel, you must first obtain a channel instance and then attach to that channel. In most instances, as a convenience, it is unnecessary to explicitly attach a channel as it will implicitly attached when performing any operation on the channel such as publishing or subscribing.

Obtaining a channel instance

A Channel object is a reference to a single channel. A channel instance is obtained from the channels collection of the Realtime::ClientRealtimeARTRealtimeAblyRealtime instance, and is uniquely identified by its unicode string name. You can only connect to one channel in a single operation, so wildcards are not supported. Find out more about channel naming

var channel = realtime.channels.get('channelName');
var channel = realtime.channels.get('channelName');
Channel channel = realtime.channels.get("channelName");
IRealtimeChannel channel = realtime.Channels.Get("channelName");
channel = realtime.channels.get('channelName')
ARTRealtimeChannel *channel = [realtime.channels get:@"channelName"];
let channel = realtime.channels.get("channelName")
final channel = realtime.channels.get('channelName');

Setting channel options

You can set channel options in one of two ways:

Setting channel parameters

Channel parameters allow a client to set the properties of a channel. The are two parameters that can be specified:

rewind
Used to request that an attachment start from a given number of messages or point in time in the past. See rewind for more information.
delta
Used to request that data payloads should be sent as deltas to the previous payload. See deltas for more information.
const realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
const channelOptions = {
  params: {
    delta: 'vcdiff',
    rewind: '1'
  }
};
const channel = realtime.channels.get('den-oak-old', channelOptions);
const realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
const channelOptions = {
  params: {
    delta: 'vcdiff',
    rewind: '1'
  }
};
const channel = realtime.channels.get('den-oak-old', channelOptions);
Setting channel encryption options

You can enable encryption on a channel using the channel options.

Find out more about symmetric message encryption.

Ably.Realtime.Crypto.generateRandomKey(function(err, key) {
  var options = { cipher: { key: key } };
  var channel = realtime.channels.get('channelName', options);
});
Ably.Realtime.Crypto.generateRandomKey(function(err, key) {
  var options = { cipher: { key: key } };
  var channel = realtime.channels.get('channelName', options);
});
CipherParams params = Crypto.getDefaultParams(key);
ChannelOptions options = new ChannelOptions();
options.encrypted = true;
options.cipherParams = params;
Channel channel = realtime.channels.get("channelName", options);
byte[] key = Crypto.GenerateRandomKey();
CipherParams cipherParams = Crypto.GetDefaultParams(key);
ChannelOptions channelOpts = new ChannelOptions(cipherParams);
IRealtimeChannel encryptedChannel = realtime.Channels.Get("channelName", channelOpts);
key = Ably::Util::Crypto.generate_random_key
options = { cipher: { key: key } }
channel = realtime.channels.get('channelName', options)
NSData *key = [ARTCrypto generateRandomKey];
ARTChannelOptions *options = [[ARTChannelOptions alloc] initWithCipherKey:key];
ARTRealtimeChannel *channel = [realtime.channels get:@"channelName" options:options];
let key = ARTCrypto.generateRandomKey()
let options = ARTChannelOptions(cipherKey: key)
let channel = realtime.channels.get("channelName", options: options)
Setting channel mode flags

Channel mode flags enable a client to specify a subset of the capabilities granted by their token or API key as channel options. Channel mode flags offer the ability for clients to use different capabilities for different channels, however, as they are flags and not permissions they cannot be enforced by an authentication server.

The channel mode flags available are:

Flag Description
SUBSCRIBE Can subscribe to receive messages on the channel.
PUBLISH Can publish messages to the channel.
PRESENCE_SUBSCRIBE Can subscribe to receive presence events on the channel.
PRESENCE Can register presence on the channel.

Channel mode flags are set as channel options:

const realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
const channelOptions = {
  modes: ['PUBLISH', 'SUBSCRIBE', 'PRESENCE']
};
const channel = realtime.channels.get('den-oak-old'), channelOptions);

Note: Channel mode flags enable clients to be present on a channel without subscribing to presence events.

Subscribing to a channel

To subscribe to a channel, use the subscribe method of a channel:

var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('den-oak-old');
channel.subscribe(function(message) {
  alert('Received: ' + message.data);
});
var Ably = require('ably');
var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('den-oak-old');
channel.subscribe(function(message) {
  console.log("Received: " + message.data);
});
realtime = Ably::Realtime.new('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ')
channel = realtime.channels.get('den-oak-old')
channel.subscribe do |message|
  puts "Received: #{message.data}"
end
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
Channel channel = realtime.channels.get("den-oak-old");
channel.subscribe(new MessageListener() {
  @Override
  public void onMessage(Message message) {
    System.out.println("New messages arrived. " + message.name);
  }
});
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
IRealtimeChannel channel = realtime.Channels.Get("den-oak-old");
channel.Subscribe(message => {
  Console.WriteLine($"Message: {message.Name}:{message.Data} received");
});
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ"];
ARTRealtimeChannel *channel = [realtime.channels get:@"den-oak-old"];
[channel subscribe:^(ARTMessage *message) {
    NSLog(@"Received: %@", message.data);
}];
let realtime = ARTRealtime(key: "xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ")
let channel = realtime.channels.get("den-oak-old")
channel.subscribe { message in
    print("Received: \(message.data)")
}
final realtime = ably.Realtime(key: 'xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
final channel = realtime.channels.get('den-oak-old');
final channelMessageSubscription = channel
  .subscribe()
  .listen((ably.Message message) {
    print('Received: ${message.data}');
  }
);

Subscribing to a channel with the rewind option enabled

It is possible to subscribe to a channel and obtain messages from that channel’s history with a single API request. To do so you would use the rewind feature. This will result in the specified number of messages from history being returned as part of the subscription process.

Rewind has two methods of obtaining messages from history. You can retrieve a specified number of messages from history or messages from a set period of time into the past.

In current Ably libraries, or when using the service without a library, this is done by qualifying the channel name. It is possible to specify channel params directly via the API. As an example, if you have no metadata and wish to subscribe to channel my_channel and fetch the most recent message from history, you would specify the channel as [?rewind=1]my_channel. If the channel had some metadata, [some_metadata]my_channel, you would apply rewind with [some_metadata?rewind=1]my_channel. For more details, look at our channel parameter documentation.

Note that rewind is limited to a maximum of 100 messages, and can only get messages no older than 2 minutes by default. If a channel has persistence enabled, then it is possible to rewind back in time by up to the persistence TTL on the channel.

In addition, rewind will only apply upon attaching to a channel, so any subsequent subscriptions post-attach will not fetch old messages.

Rewind by number of messages

To rewind by a set number of messages, add ?rewind=NUM_MSG to your metadata, where NUM_MSG is the number of messages you wish to get at most. For example, to obtain 1 message from history as you subscribe, you would do the following:

var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('[?rewind=1]den-oak-old');
channel.subscribe(function(message) {
  alert('Received: ' + message.data);
});
var Ably = require('ably');
var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('[?rewind=1]den-oak-old');
channel.subscribe(function(message) {
  console.log("Received: " + message.data);
});
realtime = Ably::Realtime.new('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ')
channel = realtime.channels.get('[?rewind=1]den-oak-old')
channel.subscribe do |message|
  puts "Received: #{message.data}"
end
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
Channel channel = realtime.channels.get("[?rewind=1]den-oak-old");
channel.subscribe(new MessageListener() {
  @Override
  public void onMessage(Message message) {
    System.out.println("New messages arrived. " + message.name);
  }
});
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
IRealtimeChannel channel = realtime.Channels.Get("[?rewind=1]den-oak-old");
channel.Subscribe(message => {
  Console.WriteLine($"Message: {message.Name}:{message.Data} received");
});
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ"];
ARTRealtimeChannel *channel = [realtime.channels get:@"[?rewind=1]den-oak-old"];
[channel subscribe:^(ARTMessage *message) {
    NSLog(@"Received: %@", message.data);
}];
let realtime = ARTRealtime(key: "xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ")
let channel = realtime.channels.get("[?rewind=1]den-oak-old")
channel.subscribe { message in
    print("Received: \(message.data)")
}
Rewind by period of time

To rewind by a time interval, add ?rewind=TIME to your metadata, where TIME is the time specifier. For example, to obtain 10 seconds of messages from history as you subscribe, you would do the following:

var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('[?rewind=10s]den-oak-old');
channel.subscribe(function(message) {
  alert('Received: ' + message.data);
});
var Ably = require('ably');
var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('[?rewind=10s]den-oak-old');
channel.subscribe(function(message) {
  console.log("Received: " + message.data);
});
realtime = Ably::Realtime.new('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ')
channel = realtime.channels.get('[?rewind=10s]den-oak-old')
channel.subscribe do |message|
  puts "Received: #{message.data}"
end
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
Channel channel = realtime.channels.get("[?rewind=10s]den-oak-old");
channel.subscribe(new MessageListener() {
  @Override
  public void onMessage(Message message) {
    System.out.println("New messages arrived. " + message.name);
  }
});
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
IRealtimeChannel channel = realtime.Channels.Get("[?rewind=10s]den-oak-old");
channel.Subscribe(message => {
  Console.WriteLine($"Message: {message.Name}:{message.Data} received");
});
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ"];
ARTRealtimeChannel *channel = [realtime.channels get:@"[?rewind=1]den-oak-old"];
[channel subscribe:^(ARTMessage *message) {
    NSLog(@"Received: %@", message.data);
}];
let realtime = ARTRealtime(key: "xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ")
let channel = realtime.channels.get("[?rewind=10s]den-oak-old")
channel.subscribe { message in
    print("Received: \(message.data)")
}

Note that currently only seconds (10s) and minutes (2m) are supported as time periods.

Publishing on a channel

In order to publish on a channel, you simply need to make use of the publish method:

var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('den-oak-old');
channel.publish('example', 'message data');
var Ably = require('ably');
var realtime = new Ably.Realtime('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
var channel = realtime.channels.get('den-oak-old');
channel.publish("example", "message data");
realtime = Ably::Realtime.new('xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ')
channel = realtime.channels.get('den-oak-old')
channel.publish 'example', 'message data'
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
Channel channel = realtime.channels.get("den-oak-old");
channel.publish("example", "message data");
AblyRealtime realtime = new AblyRealtime("xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ");
IRealtimeChannel channel = realtime.Channels.Get("den-oak-old");
channel.Publish("example", "message data");
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ"];
ARTRealtimeChannel *channel = [realtime.channels get:@"den-oak-old"];
[channel publish:@"example" data:@"message data"];
let realtime = ARTRealtime(key: "xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ")
let channel = realtime.channels.get("den-oak-old")
channel.publish("example", data: "message data")
final realtime = ably.Realtime(key: 'xVLyHw.zZBL4A:CghbBWMU2ciHx2B-YTvrufDy9I9zUF3CPmztRgrwVzQ');
final channel = realtime.channels.get('den-oak-old');
await channel.publish(name: 'example', data: 'message data');

Channel lifecycle

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. Within the dashboard for your app however, you can pre-configure one or more channel namespaces (i.e. name prefixes), and associate different attributes and access rights with those namespaces. Find out more about channel namespaces.

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:

realtime.channels.get('chatroom').attach(function(err) {
  console.log('"chatroom" exists and is now available globally in every datacenter');
});
realtime.channels.get('chatroom').attach(function(err) {
  console.log('"chatroom" exists and is now available globally in every datacenter');
});
realtime.channels.get('chatroom').attach do |channel|
  puts "'chatroom' exists and is now available globally in every datacenter"
end
Channel channel = realtime.channels.get("chatroom");
channel.on(new ChannelStateListener() {
  @Override
  public void onChannelStateChanged(ChannelStateChange state) {
    switch (state.current) {
      case attached: {
        System.out.println("'chatroom' exists and is now available globally");
      }
    }
  }
});
IRealtimeChannel channel = realtime.Channels.Get("chatroom");
channel.Attach((success, error) => {
  Console.WriteLine("'chatroom' exists and is now available globally");
});
[[realtime.channels get:@"chatroom" options:options] attach:^(ARTErrorInfo *error) {
  NSLog(@"'chatroom' exists and is now available globally in every datacenter");
}];
realtime.channels.get("chatroom").attach { error in
  print("'chatroom' exists and is now available globally in every datacenter")
}
final channel = realtime.channels.get('den-oak-old');
final channelMessageSubscription = channel
  .on()
  .listen((ably.ChannelStateChange state) {
    switch (state.current) {
      case ably.ChannelStateChange.attached: {
        print("'chatroom' exists and is now available globally");
      }
    }
  }
);

Clients attach to a channel in order to participate on that channel in any way (either to publish, subscribe, or be present on the channel). It is worth noting that some libraries allow for publishing to a channel without attaching to it. See if your chosen SDK supports this.

Channel metadata

Ably provides various APIs for obtaining metadata (such as querying the current status of a channel, or subscribing to channel lifecycle or channel occupancy events), see our Channel Metadata documentation.

Implicit attach

Although the attach operation can be initiated explicitly by a client, it is more common for the client to subscribe, which will initiate the attach if the channel is not already attached. This client library allows clients to begin publishing messages without attaching to the channel with transient publishing.

var channel = realtime.channels.get('chatroom');
channel.subscribe('action', function(message) { // implicit attach
  console.log('Message received '' + message.data);
});
channel.publish('action', 'boom!');
var channel = realtime.channels.get('chatroom');
channel.subscribe('action', function(message) { // implicit attach
  console.log('Message received '' + message.data);
});
channel.publish('action', 'boom!');
channel = realtime.channels.get('chatroom')
channel.subscribe('action') do |message| # implicit attach
  puts "Message received: #{message}";
end
channel.publish 'action', 'boom!'
Channel channel = realtime.channels.get("chatroom");
/* Implicit attach when subscribing */
channel.subscribe(new MessageListener() {
  @Override
  public void onMessage(Message message) {
    System.out.println("Message received: " + message.data);
  }
});
channel.publish("action", "boom!");
IRealtimeChannel channel = realtime.Channels.Get("chatroom");
channel.Subscribe(message => Console.WriteLine("Message received: " + message.Data));
channel.Publish("action", "boom");
ARTRealtimeChannel *channel = [realtime.channels get:@"chatroom" options:options];
[channel subscribe:@"action" callback:^(ARTMessage *message) {
    NSLog(@"Message received: %@", message.data);
}]
[channel publish:@"action" data:@"boom!"];
let channel = realtime.channels.get("chatroom")
channel.subscribe("action") { message in
    print("Message received: \(message.data)")
}
channel.publish("action", data: "boom!")
final channel = realtime.channels.get('chatroom');
/* Implicit attach when subscribing */
channel.subscribe(name: 'action').listen((ably.Message message) {
  print('Received: ${message.data}');
});
channel.publish(name: 'action', data: 'boom!');

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

Difference between attaching and subscribing

It is important to understand the difference between attaching and subscribing to a channel, and that messages are sent to clients as soon as they attach to a channel.

Published messages are immediately sent to clients on attaching to a channel as long as they have subscribe capabilities for that channel. Messages are sent regardless of whether or not the client has subscribed to the channel.

Subscribing to a channel registers a subscribe listener for messages received on the channel and is a client-side operation, meaning that Ably is unaware of whether or not a client is subscribed to a channel.

As subscribing to a channel implicitly attaches a client, it is important to note that if a client subscribes to and then unsubscribes from a channel, the client remains attached. The client will continue to be sent published messages until they detach from the channel.

Modifying channel options

You can modify the ChannelOptions associated with a given channel instance by calling setOptions and passing a new ChannelOptions. The modified options will either take effect at the time of attachment (if an attach for that channel has not yet been initiated), or the setOptions call will trigger an immediate attach operation to apply the modified options. Success or failure of any triggered attach operation triggered is indicated in the result of the setOptions call.

Publishing to multiple channels

Often it is necessary to publish a single message in multiple channels at the same time. In the realtime API, this is achieved simply by making multiple separate publish requests. If a separate publish is made in each of the channels in question, the realtime protocol will allow for those concurrent requests to be in-flight simultaneously. This ensures that a publish on a channel is not delayed waiting for completion of operations in other channels.

It is also possible to publish one or more messages into multiple channels in a single operation using the REST batch API.

Transient publishing

When attempting to only publish to a client, it is possible to publish without attaching to the channel. This can be beneficial if you intend to publish into many channels, removing the need to attach to a channel each time you wish to publish. Additionally, it avoids the client subscribing to messages, avoiding messages being sent to it redundantly.

Note that transient publishing is only available for certain libraries, otherwise publishing will also attach you to the channel.

var channel = realtime.channels.get('chatroom');
// The publish below will not attach you to the channel
channel.publish('action', 'boom!');
var channel = realtime.channels.get('chatroom');
// The publish below will not attach you to the channel
channel.publish('action', 'boom!');
channel = realtime.channels.get('chatroom')
# The publish below will not attach you to the channel
channel.publish 'action', 'boom!'
let channel = realtime.channels.get("chatroom")
// The publish below will not attach you to the channel
channel.publish("action", data: "boom!")
final channel = realtime.channels.get('den-oak-old');
// The publish below will not attach you to the channel
await channel.publish(name: 'example', data: 'message data');

Channel states

A channel can exist in any of the following states:

initializedInitialized
A Channel object having this state has been initialized but no attach has yet been attempted
attachingAttaching
An attach has been initiated by sending a request to Ably. This is a transient state; it will be followed either by a transition to attached, suspended, or failed
attachedAttached
Attach has succeeded. In the attached state a client may publish and subscribe to messages, or be present
detachingDetaching
A detach has been initiated on the attached Channel by sending a request to Ably. This is a transient state; it will be followed either by a transition to detached or failed
detachedDetached
The Channel, having previously been attached, has been detached by the user
suspendedSuspended
The Channel, having previously been attached, has lost continuity, usually due to the client being disconnected from Ably for more than two minutes. It will automatically attempt to reattach as soon as connectivity is restored
failedFailed
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)

Listening for state changes

The Channel object is an EventEmitter and emits an event whose name is the new state whenever there is a channel state change. Register a channel state change listener with the onOn or onceOnce methods, depending on whether you want to monitor all state changes, or only the first occurrence of one. Remove channel state listeners with the offOff 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 the History api to retrieve them.

channel.on('attached', function(stateChange) {
  console.log('channel ' + channel.name + ' is now attached');
  console.log('Message continuity on this channel ' + \
    (stateChange.resumed ? 'was' : 'was not') + ' preserved');
});
channel.on('attached', function(stateChange) {
  console.log('channel ' + channel.name + ' is now attached');
  console.log('Message continuity on this channel ' + \
    (stateChange.resumed ? 'was' : 'was not') + ' preserved');
});
channel.on(:attached) do |channel_state_change|
  puts "channel #{channel.name} is now attached"
  puts "Message continuity #{channel_state_change.resumed ? 'was' : 'was not'} preserved"
end
channel.on(ChannelEvent.attached, new ChannelStateListener() {
  @Override
  public void onChannelStateChanged(ChannelStateChange stateChange) {
    System.out.println("channel " + channel.name + " is now attached");
    if (stateChange.resumed) {
      System.out.println("Message continuity was preserved");
    } else {
      System.out.println("Message continuity was not preserved");
    }
  }
});
IRealtimeChannel channel = realtime.Channels.Get("chatroom");
channel.On(ChannelEvent.Attached, stateChange => {
  Console.WriteLine("channel " + channel.Name + " is now attached");
  if (stateChange.resumed) {
    Console.WriteLine("Message continuity was preserved");
  } else {
    Console.WriteLine("Message continuity was not preserved");
  }
});
[channel on:ARTChannelEventAttached callback:^(ARTChannelStateChange *stateChange) {
  NSLog(@"channel %@ is now attached", channel.name);
  if (stateChange.resumed) {
    NSLog(@"Message continuity was preserved");
  } else {
    NSLog(@"Message continuity was not preserved");
  }
}];
channel.on(.attached) { stateChange in
  print("channel \(channel.name) is now attached")
  if (stateChange.resumed) {
    print("Message continuity was preserved")
  } else {
    print("Message continuity was not preserved")
  }
}
final stateChangeListener = channel
  .on(ably.ChannelEvent.attached)
  .listen((ably.ChannelStateChange state) {
    print('channel ${channel.name} is now attached');
    if (stateChange.resumed) {
      print('Message continuity was preserved');
    } else {
      print('Message continuity was not preserved');
    }
  }
);

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

var myListener = function(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);
var myListener = function(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);
channel.on do |channel_state_change|
  puts "channel state is #{channel_state_change.current}"
end
channel.on(new ChannelStateListener() {
  @Override
  public void onChannelStateChanged(ChannelStateChange stateChange, ErrorInfo reason) {
    System.out.println("channel state is " + ChannelState.values()[stateChange.current]);
  }
});
channel.On(stateChange => Console.WriteLine("channel state is " + stateChange.Current));
ARTEventListener *listener = [channel on:^(ARTChannelStateChange *stateChange) {
    NSLog(@"channel state is %@", stateChange.current);
}];
let listener = channel.on { stateChange in
    print("channel state is \(stateChange.current)")
}
final stateChangeListener = channel
  .on()
  .subscribe((ably.ChannelStateChange stateChange) {
    print('channel state is ${stateChange.current.toString().split('.').last}');
  }
);

Previously registered listeners can be removed individually or all together.

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

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

/* remove the listener registered for all events */
channel.off(myListener);
# remove the listener proc registered for a single event
channel.off(:attached, &my_proc)

# remove the listener proc registered for all events
channel.off(&my_proc)
/* remove the listener registered for a single event */
channel.off(ChannelEvent.attached, channelStateListener);

/* remove the listener registered for all events */
channel.off(channelStateListener);
// remove the listener registered for a single event
channel.Off(ChannelEvent.Attached, channelStateListener);

// remove the listener registered for all events
channel.Off(channelStateListener);
// remove the listener registered for a single event
[channel off:ARTChannelEventAttached listener:listener];

// remove the listener registered for all events
[channel off:listener];
// remove the listener registered for a single event
channel.off(.attached, listener: listener)

// remove the listener registered for all events
channel.off(listener)
// cancel stream subscription on the listener to stop receiving the events
stateChangeListener.cancel();

Detaching from a channel

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.

channel.detach();
channel.on('detached', function(stateChange) {
  console.log('detached from the channel ' + channel.name);
};
channel.detach();
channel.on('detached', function(stateChange) {
  console.log('detached from the channel ' + channel.name);
};
channel.detach
channel.on(:detached) do |channel_state_change|
  puts "detached from the channel #{channel.name}"
end
channel.detach();
channel.on(ChannelEvent.detached, new ChannelStateListener() {
  @Override
  public void onChannelStateChanged(ChannelState state, ErrorInfo reason) {
    System.out.println("detached from the channel " + channel.name);
    if (reason != null) System.out.println(reason.toString());
  }
});
Channel.Detach();
channel.On(ChannelEvent.Detached, stateChange => {
  Console.WriteLine("detached from the channel " + channel.Name)
});
[channel detach]
[channel on:ARTChannelEventDetached callback:^(ARTChannelStateChange *stateChange) {
  NSLog(@"detached from the channel ", channel.name);
}];
channel.detach()
channel.on(.detached) { stateChange in
  print("detached from the channel \(channel.name)")
}
channel.detach();
final stateChangeListener = channel
  .on(ably.ChannelEvent.detached)
  .listen((ably.ChannelStateChange state) {
    print('detached from the channel ${channel.name}');
  }
);

A channel will automatically close when there are no more realtime clients attached to it and approximately one minute has passed since the last client detached and since the last message was published to the channel.

Handling channel failures

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.

The client libraries 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 availablewaiting for a result when explicitly calling attach.

realtime.channels.get('private:chatroom').attach(function(err) {
  if (err) {
    console.error('Attach failed: ' + err);
  }
});
realtime.channels.get('private:chatroom').attach(function(err) {
  if (err) {
    console.error('Attach failed: ' + err);
  }
});
deferrable = realtime.channels.get('private:chatroom').attach
deferrable.errback do |error|
  puts "Attach failed: #{error}"
end
Channel channel = realtime.channels.get("private:chatroom");
channel.on(new ChannelStateListener() {
  @Override
  public void onChannelStateChanged(ChannelStateChange stateChange, ErrorInfo reason) {
    switch (stateChange.current) {
      case failed: {
        System.out.println("Attach failed: " + reason.message);
      }
    }
  }
});
channel.attach();
IRealtimeChannel privateChannel = realtime.Channels.Get("private:chatroom");
privateChannel.Attach((_, error) => {
    if (error != null)
    {
        Console.WriteLine("Attach failed: " + error.Message);
    }
});
[[realtime.channels get:@"private:chatroom"] attach:^(ARTErrorInfo *error) {
    if (error) {
        NSLog(@"Attach failed: %@", error);
    }
}];
realtime.channels.get("private:chatroom").attach { error in
    if let error = error {
        print("Attach failed: \(error)")
    }
}
Channel channel = realtime.channels.get('private:chatroom');
channel.on().listen((ably.ChannelStateChange stateChange) {
  switch (stateChange.current) {
    case ably.ChannelStateChange.failed: {
      System.out.println('Attach failed: ${stateChange.reason.message}');
    }
  }
});
channel.attach();

Fatal channel errors

Some classes of errors are fatal. These cause the channel to move to the FAILED state. The client library will not attempt any automatic recovery actions. For example: attempting to attach to a channel that the token you’re using does not have the subscribe capability for will cause that channel to enter the FAILED state.

Note that while fatal errors won’t get better on their own, they can be 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 use Auth#authorize() to obtain a new token which does have the right capabilities, then call attach() on the channel. While the library will not automatically reattach in the FAILED state, explicit calls to attach() will make the client try again.

Nonfatal errors

Other types of errors are nonfatal. For example, a client may have network connectivity issues, or a channel may experience a loss of strict message continuity. The library will automatically attempt to recover from these events. If channel continuity is lost in the process, the library will notify you though a resumed flag in the ATTACHED or UPDATE event, so you can decide how to handle that 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. So for example:

  • The first time you attach to a channel on a fresh connection, resumed will be false, as there was nothing to continue from.
  • If you successfully recover a connection and reattach to your channels, the resumed flag on the ATTACHED events will tell you whether messages continuity was preserved. Any channel for which it’s true is guaranteed to receive every message it missed while the client was disconnected.
  • If you resume or recover a connection unsuccessfully (so you get a fresh connection) — generally because you were disconnected for more than two minutes, which is how long Ably holds connection state for — continuity is lost. If you were resuming, all your channels (which will have gone into the SUSPENDED state after two minutes) will still reattach automatically, and you will get ATTACHED events with resumed false.
  • If Ably needs to signal a loss of message continuity on an attached channel (for example, during a partially successful resume where the client was disconnected for less than two minutes), you will get an UPDATE event with resumed false.

Connection state change side effects on channels

  • 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

Channel namespaces

One or more channel rules may be configured for an app in your dashboard. These are rules which apply to a channel based on its ‘namespace’. The namespace is the first colon-delimited segment of its name (from the start, up to and including, the last character before the :). If the channel name contains no colon, the namespace is the entire channel name.

For example, the following channels are all part of the “public” namespace:

  • public
  • public:events
  • public:news:americas

Note that wildcards are not supported in channel namespaces.

The namespace attributes that can be configured are:

Persist last message
if enabled, the very last message published on a channel will be stored for an entire year, retrievable using the channel rewind mechanism by attaching to the channel with rewind=1. If you send multiple messages atomically in a single protocol message, for example with publish([{...}, {...}, {...}]), you would receive all of them as one message. Only messages are stored, not presence messages. This last message storage is not accessible using the normal history API, only through rewind. Please note that for the message stored, an additional message is deducted from your monthly allocation.
Persist all messages
if enabled, all messages within this namespace will be stored according to the storage rules for your account (24 hours for free accounts). You can access stored messages via the history API. Please note that for each message stored, an additional message is deducted from your monthly allocation.
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.
Push notifications enabled
If checked, publishing messages with a push payload in the extras field is permitted and can trigger the delivery of a push notification to registered devices for the channel. Find out more about push notifications.
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. Find out more about message interactions.

Key or token capabilities can also specify access rights based on channel namespace. Find out more about authentication.

Presence

Channels expose a presencePresence member which a client can use to obtain channel presence information and to enter and leave the presence channel itself. See the presence documentation for details.

API Reference

View the Channels and Channel API Reference.


Need help?

If you need any help with your implementation or if you have encountered any problems, do get in touch. You can also quickly find answers from our knowledge base, and blog.