Push Notifications - Device activation and subscription

Every device that will receive push notifications must register itself with the platform specific push notification service (APNs on iOS, FCM on Android). The Ably client libraries provide a consistent API for registration across all platforms, including device registration and receiving push notifications via Ably channels sent from other platforms.


Push Notifications process using Ably

The client libraries also provide a set of admin functionality that is typically used server-side (in a trusted environment) to manage all devices and push notification delivery and subscriptions. You can find out more in the push admin documentation.

In this section, we will run you through all of the features that a push notification device has available to it.

Platform installation

Whilst platform installation is platform-specific, all subsequent Ably API calls are generally portable. Be sure to choose a language above that you wish to see the documentation and code examples in.

Before you can activate your push device or receive push notifications, you must first plug in Ably to the underlying OS or platform. Once Ably is plugged in, all subsequent API interactions you have will be with the Ably Realtime library API which is as consistent as possible across all platforms. By providing a consistent API interface across all platforms, we aim to ensure implementation is simpler and more predictable regardless of the platform you are integrating Ably with.

Installation, however, is platform-specific and as such, instructions for each platform and service are provided below:

On Android

If you do not have a Firebase project, create a new Firebase project and add your Android app to the project. Then setup Firebase Cloud Messaging in your Android application. Then create and add a server key on your firebase project, and enter it in your Ably app’s dashboard (App > Notifications tab > Push Notifications Setup > Setup Push Notifications).

To receive messages on an Android device, you must implement a service that extends FirebaseMessagingService. To handle incoming push notifications, override the onMessageReceived() method.

These messages are sent via the Ably platform as RemoteMessage instances. However, Ably clients are unaware of them so you should implement logic to handle the push notification messages yourself. For example, by displaying a notification on the device.

To push messages to specific devices, Ably uses the device’s registration token. Keep Ably updated with the latest device registration token by overriding FirebaseMessagingService#onNewToken and calling ActivationContext#onNewRegistrationToken. Your service extending “FirebaseMessagingService” should look like the following:

public class AblyPushNotificationService extends FirebaseMessagingService {
  public static final String PUSH_NOTIFICATION_ACTION = AblyPushNotificationService.class.getName() + ".PUSH_NOTIFICATION_MESSAGE";

  @Override
  public void onMessageReceived(@NonNull RemoteMessage message) {
      // TODO Handle the message delivered to the device.
  }

  @Override
  public void onNewToken(@NonNull String s) {
    super.onNewToken(s);
    //Store token in Ably
    ActivationContext.getActivationContext(this).onNewRegistrationToken(RegistrationToken.Type.FCM, s);
  }
}

On iOS

Create an APNs .p12 certificate and add it to your application in your Ably dashboard (App > Notifications tab > Push Notifications Setup > Setup Push Notifications). An Apple Developer Program membership is required to do this. This is used by Ably to authenticate with APNs using certificate-based authentication. Then, register your app with APNs.

In UIApplicationDelegate#didFinishLaunchingWithOptions, call UIApplication.shared.registerForRemoteNotifications. An example showing how to do this is available in the iOS Push Notifications tutorial.

You also need to override two methods in UIApplicationDelegate: application(_:didRegisterForRemoteNotificationsWithDeviceToken:)application:didRegisterForRemoteNotificationsWithDeviceToken: and application(_:didFailToRegisterForRemoteNotificationsWithError:)application:​did​Fail​To​Register​For​Remote​Notifications​With​Error:​. Provide Ably with the deviceToken and error from the delegate methods by calling the 2 methods ARTPush, didRegisterForRemoteNotificationsWithDeviceToken and didFailToRegisterForRemoteNotificationsWithError. You can provide either a rest or realtime client, which is used to authenticate with Ably.

// In your UIApplicationDelegate class:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    [ARTPush didRegisterForRemoteNotificationsWithDeviceToken:deviceToken realtime:[self getAblyRealtime]];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
    [ARTPush didFailToRegisterForRemoteNotificationsWithError:error realtime:[self getAblyRealtime]];
}

- (ARTRealtime *)getAblyRealtime {
    ARTClientOptions *options = [[ARTClientOptions alloc] init];
    // Set up options; API key or auth URL, etc.
    return [[ARTRealtime alloc] initWithOptions: options];
}
// In your UIApplicationDelegate class:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    ARTPush.didRegisterForRemoteNotifications(withDeviceToken: deviceToken, realtime: self.getAblyRealtime())
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    ARTPush.didFailToRegisterForRemoteNotificationsWithError(error, realtime: self.getAblyRealtime())
}

func getAblyRealtime() -> ARTRealtime {
    var options = ARTClientOptions()
    // Set up options; API key or auth URL, etc.
    return ARTRealtime(options: options)
}

Finally, to handle remote push notifications, implement application(_:didReceiveRemoteNotification:fetchCompletionHandler:)@application:didReceiveRemoteNotification:fetchCompletionHandler:
@
.

To handle user interaction with your push notification, implement didReceive(_:completionHandler:)didReceiveNotificationResponse:completionHandler:.

Activating push on your device

Activating a device for push notifications and registering it with Ably is commonly performed entirely from the device. However, it is possible to separate the concerns such that activation with the underlying platform is performed on the device, and registration of that activated device with Ably is performed using your own servers. This latter pattern is more commonly used when you want to minimize the capabilities assigned to an untrusted device. Find out how to register the device from your servers


Push Notifications direct device registration

In the following example, we will both activate the device with the underlying platform and register the device with Ably from the device itself.

Activate the device for push with push.activate

If you want to start receiving push notifications from Ably (e.g. from your main activityyour UIApplicationDelegate), you need to first call AblyRealtime.push.activateARTRealtime.push.activate which will register the device for push by doing the following on your behalf:

  • Ensure the Ably client is authenticated
  • Generate a unique identifier for this device and store this in local storage
  • Activate the device for push notifications with the underlying OS or platform and obtain a unique identifier for the device as a push recipient. For example, in FCM this is described as a registration token, and in APNs this is described as a device token
  • Register the local device with Ably using the device’s unique identifier, platform-specific details such as form factor and OS, and the push recipient details to receive push notifications. This in turn ensures Ably can reach this device and deliver push notifications
  • Store the deviceIdentityToken from the response from Ably in local storage so that subsequent requests to Ably to update push recipient details are authenticated as being from the device in question.

Please note that the effects of calling activate outlives the current process. Once called, the device will stay registered even after the application is closed, and up until deactivate is called. activate is idempotent: calling it again when device is already activated has the sole effect of calling its callback.

AblyRealtime ably = getAblyRealtime();
ably.setAndroidContext(context)
ably.push.activate();
ARTRealtime *ably = [self getAblyRealtime];
[ably.push activate];
let ably = self.getAblyRealtime()
ably.push.activate()

Please bear in mind that in order for the client to register itself automatically with Ably, it needs to be authenticated and have the required push-subscribe capability. If you would prefer to delegate registration of the push device to your own servers and not allow devices to register themselves directly with Ably, then see the section how to register devices from your server. You can also check our recommendations for choosing either registration strategy.

Handle push notification lifecycle

When activate and deactivate are called, the device registers with FCM/APNs, and activation with Ably begins. Additionally, when a push token (APNs device token or FCM registration token) is updated on the device, the Ably SDK attempts to update Ably’s servers with the new token. The activation, deactivation and update process might fail, and therefore you should handle the result of these methods in the push notification lifecycle methods.

There are three events which correspond to the three broadcast intents you should listen todelegate methods you should implement:
- Push activation completion:= (Success or failure). Listen for the io.ably.broadcast.PUSH_ACTIVATE action in a broadcast intentImplement - (void)didActivateAblyPush:(nullable ARTErrorInfo *)error;func didActivateAblyPush(_ error: ARTErrorInfo?). Once the device has successfully been activated, you can subscribe to push notifications on channels to receive push notifications from Ably.
- Push deactivation completion:= (Success or failure). Listen for the io.ably.broadcast.PUSH_DEACTIVATE action in a broadcast intentImplement - (void)didDeactivateAblyPush:(nullable ARTErrorInfo *)errorfunc didDeactivateAblyPush(_ error: ARTErrorInfo?)
- Push update completion:= (Failure only). Listen for the io.ably.broadcast.PUSH_UPDATE_FAILED action in a broadcast intentImplement - (void)didAblyPushRegistrationFail:(nullable ARTErrorInfo *)error;func didAblyPushRegistrationFail(_ error: ARTErrorInfo?)

The following example demonstrates how to listen to the push activation event:

LocalBroadcastManager.getInstance(context.getApplicationContext()).registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        ErrorInfo error = IntentUtils.getErrorInfo(intent);
        if (error != null) {
            // Handle error
            return;
        }
        // Subscribe to channels / listen for push etc.
    }
}, new IntentFilter("io.ably.broadcast.PUSH_ACTIVATE"));

ably.push.activate(context);
// Add the activate method from 'ARTPushRegistererDelegate' to your 'UIApplicationDelegate' class:
- (void)didActivateAblyPush:(nullable ARTErrorInfo *)error {
    if (error) {
        // Handle error
        return;
    }
    // Subscribe to channels / listen for push etc.
}

// Inside your AppDelegate, set the delegate on ARTClientOptions
ARTClientOptions *const options = [[ARTClientOptions alloc] init];
options.pushRegistererDelegate = self; // Or another dedicated delegate class

// Call activate, which will call the delegate method when done:
[ably.push activate];
// Add the activate method from 'ARTPushRegistererDelegate' to your 'UIApplicationDelegate' class:
func didActivateAblyPush(_ error: ARTErrorInfo?) {
    if let error = error {
        // Handle error
        return
    }
    // Subscribe to channels / listen for push etc.
}

// Inside your AppDelegate, set the delegate on ARTClientOptions
let options = ARTClientOptions()
options.pushRegistererDelegate = self // Or another dedicated delegate class

// Call activate, which will call the delegate method when done:
ably.push.activate()

Note: if the clientOptions was not provided with a ARTPushRegistererDelegate via its pushRegistererDelegate property, the UIApplicationDelegate is automatically used if it implements ARTPushRegistererDelegate. This automatic behaviour is the older, deprecated approach.

Subscribe for push notifications

Before you subscribe to a channel for push, make sure its channel namespace is configured to explicitly enable push notifications. By default, push notifications on channels are disabled.

There are two ways a device can be subscribed to a channel: directly by its device ID, or indirectly by its associated client ID.

Subscribing by device ID

A device ID uniquely identifies a device within Ably’s services and is assigned automatically at the time the device is activated.

If your client has the push-subscribe capabilities, you can do the following:

[[realtime.channels get:@"pushenabled:foo"].push subscribeDevice:^(ARTErrorInfo *error) {
    // Check error.
}];
realtime.channels.get("pushenabled:foo").push.subscribeDevice { error
    // Check error.
}
realtime.channels.get("pushenabled:foo").push.subscribeDevice(context);

// or

realtime.channels.get("pushenabled:foo").push.subscribeDeviceAsync(context, new CompletionListener() {
    @Override
    public void onSuccess() {}

    @Override
    public void onError(ErrorInfo errorInfo) {
        // Handle error.
    }
});

If your client doesn’t have the push-subscribe permissions, you should communicate the device ID to your server so that it can subscribe on the device’s behalf. You can find your unique device ID at ARTRealtime.device.idAblyRealtime.device().id. The server must then use the push admin API to subscribe the device.

Subscribing by client ID

When a device is registered, it can be associated with a client ID. AblyRealtime.push.activateARTRealtime.push.activate takes the client ID from the AblyRealtime instance.

You can subscribe all devices associated with a client ID to a channel in a single operation; that is, create a subscription by client ID. New device registrations associated to that client ID will also be subscribed to the channel, and if a device registration is no longer associated with that client ID, it will also stop being subscribed to the channel (unless it’s also subscribed directly by device ID).

To subscribe your AblyRealtime instance’s client ID to a channel:

[[realtime.channels get:@"pushenabled:foo"].push subscribeClient:^(ARTErrorInfo *error) {
    // Check error.
}];
realtime.channels.get("pushenabled:foo").push.subscribeClient { error
    // Check error.
}
realtime.channels.get("pushenabled:foo").push.subscribeClient();

// or

realtime.channels.get("pushenabled:foo").push.subscribeClientAsync(new CompletionListener() {
    @Override
    public void onSuccess() {}

    @Override
    public void onError(ErrorInfo errorInfo) {
        // Handle error.
    }
});

Alternatively, if you want to subscribe a different client ID not currently associated with the currently authenticated realtime instance, you can use the admin API.

Push capabilities

These are the capabilities necessary to perform push operations:

  • push-subscribe: Register and deregister the local device, and subscribe and unsubscribe the local device to channels for push notifications.
  • push-admin: Register, update and deregister any device registration, and subscribe and unsubscribe to channels for push notifications. Publish push notification using the POST /push/publish endpoint (AblyRealtime.push.admin.publish method).

Typically, client devices subscribing for push will either have push-subscribe privileges or delegate operations to a server with push-admin privileges.

Activating devices from your server

The default for AblyRealtime.push.activate is to register the device with Ably directly from the device, but you can instead delegate that to your server. Don’t forget to register the device using the push admin API in your server.


Push Notifications device registration via server

For this, your UIApplicationDelegate must implement these optional methods from ARTPushRegistererDelegate:

- (void)ablyPushCustomRegister:(ARTErrorInfo *)error
                 deviceDetails:(ARTDeviceDetails *)deviceDetails
                      callback:(void (^)(ARTDeviceIdentityTokenDetails * _Nullable, ARTErrorInfo * _Nullable))callback {
    if (error) {
        // Handle error.
        callback(nil, error);
        return;
    }

    [self registerThroughYourServer:deviceDetails callback:callback];
}

- (void)ablyPushCustomDeregister:(ARTErrorInfo *)error d
                        deviceId:(ARTDeviceId *)deviceId
                        callback:(void (^)(ARTErrorInfo * _Nullable))callback {
    if (error) {
        // Handle error.
        callback(nil, error);
        return;
    }

    [self unregisterThroughYourServer:deviceDetails callback:callback];
}
func ablyPushCustomRegister(_ error: ARTErrorInfo?, deviceDetails: ARTDeviceDetails, callback: @escaping (ARTDeviceIdentityTokenDetails?, ARTErrorInfo?) -> Void) {
    if let e = error {
        // Handle error.
        callback(nil, e)
        return
    }

    self.registerThroughYourServer(deviceDetails: deviceDetails, callback: callback)
}

func ablyPushCustomDeregister(_ error: ARTErrorInfo?, deviceId: String, callback: ((ARTErrorInfo?) -> Void)? = nil) {
    if let e = error {
        // Handle error.
        callback(nil, e)
        return
    }

    self.unregisterThroughYourServer(deviceDetails: deviceDetails, callback: callback)
}

For this, you need to communicate back and forth with the Ably library via the application’s LocalBroadcastManager.

First, make sure you pass true as the useCustomRegisterer parameter to activate (and for deactivate).

ably.push.activate(context, true);
ably.push.deactivate(context, true);

The Ably library will then broadcast an io.ably.broadcast.PUSH_REGISTER_DEVICE action when it needs you to register from your server, and io.ably.broadcast.PUSH_DEREGISTER_DEVICE when it needs you to deregister. You must configure a listener to those actions in your application’s AndroidManifest.xml, and from it answer back with a PUSH_DEVICE_REGISTERED or PUSH_DEVICE_DEREGISTERED, like this:

<receiver android:name=".MyAblyBroadcastReceiver" >
  <intent-filter>
     <action android:name="io.ably.broadcast.PUSH_REGISTER_DEVICE" />
     <action android:name="io.ably.broadcast.PUSH_DEREGISTER_DEVICE" />
  </intent-filter>
</receiver>
public class MyAblyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        AblyRealtime ably = getAblyRealtime();
        String action = intent.getAction();

        if (action.equals("io.ably.broadcast.PUSH_REGISTER_DEVICE")) {
            DeviceDetails device = ably.device(context);
            boolean isNew = intent.getBooleanExtra("isNew", false);

            Intent response = new Intent("io.ably.broadcast.PUSH_DEVICE_REGISTERED");

            try {
                String deviceIdentityToken = registerThroughYourServer(device, isNew);
                response.putExtra("deviceIdentityToken", deviceIdentityToken);
            } catch(AblyException e) {
                IntentUtils.addErrorInfo(intent, e.errorInfo);
            }

            LocalBroadcastManager.getInstance(context.getApplicationContext()).sendBroadcast(intent);
        } else if (action.equals("io.ably.broadcast.PUSH_DEREGISTER_DEVICE")) {
            DeviceDetails device = ably.device(context);

            Intent response = new Intent("io.ably.broadcast.PUSH_DEVICE_DEREGISTERED");

            try {
                deregisterThroughYourServer(device.id);
            } catch(AblyException e) {
                IntentUtils.addErrorInfo(intent, e.errorInfo);
            }

            LocalBroadcastManager.getInstance(context.getApplicationContext()).sendBroadcast(intent);
        }
    }
}

Direct registration vs. registration via your server

Two options for registering devices gives you the flexibility to implement push notifications in the best way for each use case.

Direct registration means less server side code as the device registers itself and attaches to a channel. However, this means you need to trust your devices to handle this securely and efficiently. Token authentication solves this.

If you want to control all of your devices server side then registration via server is the way to go. The Ably Push Admin API lets you do this.

Ably can deliver push notifications to devices using, amongst others, Apple’s Push Notification service and Google’s Firebase Cloud Messaging service. Push notifications, unlike Ably’s channel based pub/sub messaging, do not require the device to maintain a connection to Ably, as the underlying platform or OS is responsible for maintaining its own battery-efficient transport to receive push notifications. Therefore, push notifications are commonly used to display visual notifications to users or launch a background process for an app in a battery-efficient manner.

Delivering push notifications


Push Notifications in Ably

As shown above, Ably provides two models for delivering push notifications to devices:

Direct publishing

Ably provides a REST API that allows push notifications to be delivered directly to:

  • Devices identified by their unique device ID
  • Devices identified by their assigned clientId
  • Devices identified by the recipient details of the push transport such as their unique registrationToken in the case of FCM, deviceToken in the case of APNS, or targetUrl and encryptionKey in the case of a Web device (experimental). This means is particularly useful when migrating to Ably with existing push notification target devices.

Find out more about direct push notification publishing

Channel-based broadcasting

The model for delivering push notifications to devices over channels is intentionally very similar to how messages are normally delivered using Ably’s pub/sub channel. For example, a normal message published on an Ably channel is broadcast immediately to all realtime subscribers of that channel. When broadcasting push notifications on channels, however, the process is the same with the exception that the subscribers (devices receiving push notifications) are registered in advance using our API and the message itself must contain an extra push notification payload that specifies the optional visual format and optional data payload of the push notification.

Find out more about channel-based push notification broadcasting

Activating a device and receiving notifications

Every device that will receive push notifications must register itself with the platform specific push notification service (APNs on iOS, FCM on Android). The Ably client libraries provide a consistent API for registration across all platforms, including device registration and receiving push notifications via Ably channels sent from other platforms.

Find out more about device activations and subscriptions.

Managing devices and subscriptions

Whilst the realtime client libraries provide APIs for a device to activate itself (via client.push) and subscribe for push notifications (via channel.push), those APIs are intentionally limited to actions pertaining to the device it is run on.

A separate and distinct push admin API is additionally provided in our client libraries specifically designed for use by your servers to facilitate managing and delivering push notifications across all of your registered devices. This API, amongst other things, includes features to manage registered devices, channel subscriptions and deliver push notifications directly. Currently the push admin API is available in our JavaScript, Ruby, Java/Android, PHP, Python, and iOS libraries. It is also available in our other libraries through the use of the request method, using the underlying API directly.

Find out more about the push admin API.

Platform support

Ably currently offers support for push notifications on the following platforms:

Apple Push Notifications
supported on all mobile devices running iOS and desktop devices running macOS
Firebase Cloud Messaging
supported on all Android and iOS devices, although we use FCM exclusively for Android message delivery
Experimental W3C Push API
experimental support for modern W3C compliant browsers (this does not include Apple’s Safari browser). You must request access to use this API.

API Reference

View Realtime push notifications – device activation and subscription for the associated Client Library SDK 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.