Realtime Client Library API

Messages

The Ably Realtime service allows for clients to send information with messages, which contain data the client wishes to communicate. These messages are published through channels, which other users can subscribe to in order to receive them. This scalable and resilient messaging pattern is commonly called pub/sub.

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.NsTHdQ:vTIsgGcES4z0OVAVmqy8VEaEBCyZ5fiMEv8bolYTo6Y');
var channel = realtime.channels.get('mac-gel-dam');
channel.subscribe(function(message) {
  alert('Received: ' + message.data);
});
channel.publish('example', 'message data');
var Ably = require('ably');
var realtime = new Ably.Realtime('xVLyHw.NsTHdQ:vTIsgGcES4z0OVAVmqy8VEaEBCyZ5fiMEv8bolYTo6Y');
var channel = realtime.channels.get('mac-gel-dam');
channel.subscribe(function(message) {
  console.log("Received: " + message.data);
});
channel.publish("example", "message data");
realtime = Ably::Realtime.new('xVLyHw.NsTHdQ:vTIsgGcES4z0OVAVmqy8VEaEBCyZ5fiMEv8bolYTo6Y')
channel = realtime.channels.get('mac-gel-dam')
channel.subscribe do |message|
  puts "Received: #{message.data}"
end
channel.publish 'example', 'message data'
AblyRealtime realtime = new AblyRealtime("xVLyHw.NsTHdQ:vTIsgGcES4z0OVAVmqy8VEaEBCyZ5fiMEv8bolYTo6Y");
Channel channel = realtime.channels.get("mac-gel-dam");
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.NsTHdQ:vTIsgGcES4z0OVAVmqy8VEaEBCyZ5fiMEv8bolYTo6Y");
var channel = realtime.Channels.Get("mac-gel-dam");
channel.Subscribe(message => {
  Console.WriteLine($"Message: {message.name}:{message.data} received")
});
channel.Publish("example", "message data");
ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"xVLyHw.NsTHdQ:vTIsgGcES4z0OVAVmqy8VEaEBCyZ5fiMEv8bolYTo6Y"];
ARTRealtimeChannel *channel = [realtime.channels get:@"mac-gel-dam"];
[channel subscribe:^(ARTMessage *message) {
    NSLog(@"Received: %@", message.data);
}];
[channel publish:@"example" data:@"message data"];
let realtime = ARTRealtime(key: "xVLyHw.NsTHdQ:vTIsgGcES4z0OVAVmqy8VEaEBCyZ5fiMEv8bolYTo6Y")
let channel = realtime.channels.get("mac-gel-dam")
channel.subscribe { message in
    print("Received: \(message.data)")
}
channel.publish("example", data: "message data")
final rest = ably.Rest('xVLyHw.NsTHdQ:vTIsgGcES4z0OVAVmqy8VEaEBCyZ5fiMEv8bolYTo6Y');
final channel = rest.channels.get('mac-gel-dam');
final channelMessageSubscription = channel
  .subscribe()
  .listen((ably.Message message) {
    print('New messages arrived. ${message.name}');
  }
);
await channel.publish('example', 'message data');

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

Messages

Each message published has an optional event name propertymemberattribute and a data propertymemberattribute carrying the payload of the message. Various primitive and object types are defined, portable and supported in all clients, enabling clients to be interoperable despite being hosted in different languages or environments.

The supported payload types are Strings, objects or arrays capable of JSON representation, buffers containing arbitrary binary data, and Nulls. Client libraries will detect the supplied message payload and encode the message appropriately. (Note that if sending a binary, that binary should be the entire payload; an object with a binary field within it may not be correctly encoded).

Subscribing to messages

The name propertymemberattribute of published messages does not affect the distribution of a channel message to clients but may be used as a (purely client-side) subscription filter, allowing a client to register a listener that only sees a subset of the messages received on the channel. When subscribing, a message listener can subscribe to see all messages on the channel or only a subset whose name matches a given name string.

The client can choose whether or not to receive messages that they themselves publish using ClientOptions#echoMessagesClientOptions#echo_messages.

A client can subscribe to all messages on a channel by passing a listener function to the subscribe method. The listener is passed a Message object for each message received.


A client can register for messages on a channel by implementing MessageListener and calling the subscribe(MessageListener listener) or subscribe(String name, MessageListener listener) method. The listener is passed an array of one or more Message objects when messages are received.


A client can subscribe to all messages on a channel by passing a block to the subscribe method. The block is passed a Message object for each message are received.


A client can subscribe to all messages on a channel by passing a lambda expression to the Subscribe method. The lambda is passed a Message object for each message are received.
channel.subscribe(function(message) {
  console.log('message received for event ' + message.name);
  console.log('message data:' + message.data);
});
channel.subscribe(function(message) {
  console.log('message received for event ' + message.name);
  console.log('message data:' + message.data);
});
channel.subscribe(new MessageListener() {
  @Override
  public void onMessage(Message message) {
    System.out.println("Message received: " + message.data);
  }
});
channel.Subscribe(message =>
{
    Console.WriteLine($"message received for event {message.Name}");
    Console.WriteLine($"message data: {message.Data}");
});
channel.subscribe do |message|
  puts "message received for event #{message.name}"
  puts "message data: #{message.data}"
end
[channel subscribe:^(ARTMessage *message) {
    NSLog(@"message received for event %@", message.name);
    NSLog(@"message data: %@", message.data);
}];
channel.subscribe { message in
    print("message received for event \(message.name)")
    print("message data: \(message.data)")
}
final channelMessageSubscription = channel
  .subscribe()
  .listen((ably.Message message) {
    print('message received for event ${message.name}');
    print('message data: ${message.data}');
  }
);

Alternatively a listener may be registered so that it is called only for messages having a specific event name.

channel.subscribe('myEvent', function(message) {
  console.log('message received for event ' + message.name);
  console.log('message data:' + message.data);
});
channel.subscribe('myEvent', function(message) {
  console.log('message received for event ' + message.name);
  console.log('message data:' + message.data);
});
channel.subscribe("myEvent", new MessageListener() {
  @Override
  public void onMessage(Message message) {
    System.out.println("Message received: " + message.data);
  }
});
channel.Subscribe("myEvent", message =>
{
    Console.WriteLine($"message received for event {message.Name}");
    Console.WriteLine($"message data: {message.Data}");
});
channel.subscribe('myEvent') do |message|
  puts "message received for event #{message.name}"
  puts "message data: #{message.data}"
end
channel.subscribe("myEvent") { message in
    print("message received for event \(message.name)")
    print("message data: \(message.data)")
}
[channel subscribe:@"myEvent" callback:^(ARTMessage *message) {
    NSLog(@"message received for event %@", message.name);
    NSLog(@"message data: %@", message.data);
}];
final channelMessageSubscription = channel
  .subscribe(name: 'myEvent')
  .listen((ably.Message message) {
    print('message received for event ${message.name}');
    print('message data: ${message.data}');
  }
);

Previously registered listeners can be removed individually or all together.

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

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

/* remove the listener registered for all events */
channel.unsubscribe(myListener);
/* remove a single listener */
channel.unsubscribe(myListener);

/* remove the listener registered for all events */
channel.unsubscribe("myEvent", myListener);
/* remove a single listener */
channel.Unsubscribe(myHandler);

/* remove the listener registered for all events */
channel.Unsubscribe("myEvent", myHandler);
# remove the listener proc registered for a single event
channel.unsubscribe("myEvent", &my_proc)

# remove the listener proc registered for all events
channel.unsubscribe(&my_proc)
// remove the listener registered for a single event
[channel unsubscribe:@"myEvent" listener:listener];

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

// remove the listener registered for all events
channel.unsubscribe(listener)
channelMessageSubscription.cancel();

Publishing messages

Channels expose a publishPublish method whereby a client can publish either a single message or an array of messages to a channel. A listener optionally passed in to the publishPublish method enables the client to know whether or not the operation succeeded.

channel.publish('event', 'This is my payload', function(err) {
  if(err) {
    console.log('Unable to publish message; err = ' + err.message);
  } else {
    console.log('Message successfully sent');
  }
});
channel.publish('event', 'This is my payload', function(err) {
  if(err) {
    console.log('Unable to publish message; err = ' + err.message);
  } else {
    console.log('Message successfully sent');
  }
});
deferrable = channel.publish('event', 'This is my payload') do
  puts 'Messages successfully sent'
end
deferrable.errback do |err|
  puts "Unable to publish messages; err = #{err}"
end
channel.publish("event", "This is my payload", new CompletionListener() {
  @Override
  public void onError(ErrorInfo reason) {
    System.out.println("Unable to publish message; err = " + reason.message);
  }
  @Override
  public void onSuccess() {
    System.out.println("Message successfully sent");
  }
});
channel.Publish("event", "payload", (success, error) =>
{
  if (error != null) {
    Console.WriteLine("Unable to publish message. Reason: " + error.Message);
  } else {
    Console.WriteLine("Message published successfully");
  }
});
[channel publish:@"event" data:@"This is my payload" callback:^(ARTErrorInfo *error) {
  if (error) {
    NSLog(@"Unable to publish message; err = %@", error.message);
  } else {
    NSLog(@"Message successfully sent");
  }
}];
channel.publish("event", data: "This is my payload") { error in
  if let error = error {
    print("Unable to publish message; err = \(error.message)")
  } else {
    print("Message successfully sent")
  }
}
try {
  await channel.publish('event', 'This is my payload');
  print('Message successfully sent');
} on ably.AblyException catch(e) {
  print('Unable to publish message; err = ${reason.message}');
}

Channels also expose an async version PublishAsync of the Publish call which resumes execution once the message is confirmed received. It is purely for convenience.

Result result = await channel.PublishAsync("event", "payload");
if(result.IsFailure) {
  Console.WriteLine("Unable to publish message. Reason: " + result.Error.Message);
} else {
  Console.WriteLine("Message published successfully");
}

Batch publishing

It is common for a single message to be intended for multiple channels. With a realtime connection, you can effectively send a message to multiple channels at once by allowing multiple concurrent publish operations. If you wish to send a message to multiple channels within a single operation, you can make use of the REST batch API.

Retrieving message history

Channels expose a historyHistory method providing a means for clients to obtain messages previously sent on the channel. Channel history can be used to return continuous message history up to the exact point a realtime channel was attached.

History provides access to instantaneous “live” history as well as the longer term persisted history for attached channels. If persisted history is enabled for the channel, then messages will typically be stored for 24 – 72 hours. If persisted history is not enabled, Ably retains the last two minutes of message history in memory.

The following example retrieves the first two pages of historical messages published up until the point the channel was attached.

channel.attach(function() {
  channel.history({ untilAttach: true }, function(err, resultPage) {
    if(err) {
      console.log('Unable to get channel history; err = ' + err.message);
    } else {
      console.log(resultPage.items.length + ' messages received in first page');
      if(resultPage.hasNext()) {
        resultPage.next(function(err, nextPage) { ... });
      }
    }
  });
});
channel.attach(function() {
  channel.history({ untilAttach: true }, function(err, resultPage) {
    if(err) {
      console.log('Unable to get channel history; err = ' + err.message);
    } else {
      console.log(resultPage.items.length + ' messages received in first page');
      if(resultPage.hasNext()) {
        resultPage.next(function(err, nextPage) { ... });
      }
    }
  });
});
Param[] options = new Param[]{ new Param("untilAttach", "true") };
PaginatedResult<Message> resultPage = channel.history(options);
System.out.println(resultPage.items().length + " messages received in first page");
if(resultPage.hasNext()) {
  PaginatedResult<Message> nextPage = resultPage.next();
  System.out.println(nextPage.items().length + " messages received in second page");
}
PaginatedResult<Message> history = await channel.HistoryAsync(untilAttach: true);
Console.WriteLine($"{history.Items.Count} messages received in the first page");
if (history.HasNext)
{
  PaginatedResult<Message> nextPage = await history.NextAsync();
  Console.WriteLine($"{nextPage.Items.Count} messages received in the second page");
}
channel.attach do
  channel.history(until_attach: true) do |result_page|
    puts "#{result_page.items.length} messages received in first page"
    if result_page.has_next?
      result_page.next { |next_page| ... }
    end
  end
end
[channel attach:^(ARTErrorInfo *error) {
    ARTRealtimeHistoryQuery *query = [[ARTRealtimeHistoryQuery alloc] init];
    query.untilAttach = true;
    [channel history:query callback:^(ARTPaginatedResult<ARTMessage *> *resultPage, ARTErrorInfo *error) {
        NSLog(@"%lu messages received in first page", (unsigned long)[resultPage.items count]);
        if (resultPage.hasNext) {
            [resultPage next:^(ARTPaginatedResult<ARTMessage *> *nextPage, ARTErrorInfo *error) {
                // ...
            }];
        }
    } error:nil];
}];
channel.attach { error in
    let query = ARTRealtimeHistoryQuery()
    query.untilAttach = true
    try! channel.history(query) { resultPage, error in
        let resultPage = resultPage!
        print("\(resultPage.items.count) messages received in first page")
        if resultPage.hasNext {
            resultPage.next { nextPage, error in
                // ...
            }
        }
    }
}

See the history documentation for further details of the supported query parameters.

Message interactions

Message interactions are currently only supported by the Ably JavaScript SDK. To read more about message interactions please refer to the message interactions docs.

Message interactions allow you to interact with messages previously sent to a channel. Once a channel is enabled with message interactions, messages received by that channel will contain a unique timeSerial that can be referenced by later messages.

Message interactions are a foundation used to support a wide variety of use cases. You can decide and customise what data you include in an interaction, and how this is displayed to users. Using this building block you can use message interactions to build your own functionality, such as, but not limited to:

  • Emoji reactions
  • Message updating and deleting
  • Read receipts and unread counts
  • Quoting/replying to a message
  • Message threads
  • Media previews

Message interactions enable you to build such features by adding support for a new ref object that allows you to indicate what the interaction represents, and which message it is referring to.

The ref object contains two fields:

  • type (String) – a constant representing the reason for the interaction.
  • timeSerial (String) – a unique identifier used to reference a specific message, automatically generated when messages are sent in message interaction enabled channels.

Note: If you want to limit the ability of users to use message interactions you can use JWT authenticated user claims.

Enabling message interactions

Before using message interactions, they must be enabled for channels in the Ably dashboard:

1. Login to your Ably dashboard.
2. Select the app you are working with.
3. Click Settings on the right side of the navigation bar.
4. Scroll down to Channel rules and click Add new rule.
5. Give your new rule a Namespace or provide a specific Channel ID.
6. Select the Message interactions enabled checkbox then click Create channel rule.


Message interactions in dashboard

You now have a namespace based rule you can apply to channels to enable message interactions or a specific channel with message interactions enabled.

To read more on applying rules to channels read our channel rules documentation.

Using message interactions

Once message interactions have been enabled for a channel you can start to reference the timeSerial of previous messages when you publish interactions. Messages sent to message interaction enabled channels will automatically include a timeSerial.

To reference a previous message, include the ref object inside the extras object including:

  • A type constant string defining the reason for interaction.
  • The timeserial string of the interacted message.

Note: for type constants Ably has reserved strings beginning: com.ably..

For example, if you wanted to send an emoji reaction to the previous message above you could send something such as the following:

function sendReaction(emoji) {
  channel.publish('event_name', { body: emoji, extras: { ref: { type: "com.ably.reaction", timeserial: "1656424960320-1" } } })
}
function sendReaction(emoji) {
  channel.publish('event_name', { body: emoji, extras: { ref: { type: "com.ably.reaction", timeserial: "1656424960320-1" } } })
}

Creating a Listener

Regular listeners will get all messages (including those containing a reference), but it is possible to filter specifically for messages with or without a reference by supplying a filter object. A filter object can contain any number of the following fields:

  • refTimeserial (String) – filter containing a specific message timeserial (for example: v1b25XrTDg:0).
  • refType (String) – filter for a specific reference type (for example: com.ably.reaction).
  • isRef (Boolean) – filter for messages that only do or do not reference another message.

Subscribing to interactions

Subscribing to interactions involves sending a filter object defining which interactions you’re interested in.

To subscribe to all reaction interactions:

channel.subscribe({
  refType: "com.ably.reaction"
}, onReaction); 
channel.subscribe({
  refType: "com.ably.reaction"
}, onReaction); 

Subscribe to any interaction:

channel.subscribe({
  isRef: true
}, onReference); 
channel.subscribe({
  isRef: true
}, onReference); 

Subscribe to any non-interaction:

channel.subscribe({
  isRef: false 
}, onRegularMessage);
channel.subscribe({
  isRef: false 
}, onRegularMessage);

Subscribe to interactions to a specific message:

channel.subscribe({
  refTimeserial: "v1b25XrTDg:0"
}, onReference);
channel.subscribe({
  refTimeserial: "v1b25XrTDg:0"
}, onReference);

Subscribe to any combination of the above:

channel.subscribe({
  refTimeserial: "v1b25XrTDg:0",
  refType: "com.ably.reaction",
}, onReference);
channel.subscribe({
  refTimeserial: "v1b25XrTDg:0",
  refType: "com.ably.reaction",
}, onReference);

Unsubscribing from interactions

Unsubscribing works similar to subscribing, you can unsubscribe by:

  • Passing the filter object into channel.unsubscribe.
  • Passing the filter function into channel.unsubscribe.
  • Both of the above.

For example, unsubscribe to reaction interactions:

channel.unsubscribe({
  refType: "com.ably.reaction"
});
channel.unsubscribe({
  refType: "com.ably.reaction"
});

Passing a filter object will unsubscribe every listener attached to that particular filter unless you also pass the filter function.

Note: Unsubscribing with a filter requires exactly the same filter object to be passed in.

API Reference

View the Messages 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.