Overview

On incoming events such as a new message, or an incoming call, the user often expects to receive a push notification. If the app is not active (it is in the background), push notifications are the only way to notify app about new events.

This guide explains how to configure your Android app to receive push notifications from the Client SDK.

Connect Vonage to Firebase

To receive push notifications you need to connect Vonage with Firebase. To do so, you will need the following:

  1. Your Vonage Application ID
  2. Your Vonage Application's private key (upload tool method) or a Vonage admin JWT (terminal method)
  3. Your Firebase project ID
  4. Your Firebase server key

Get a Vonage application ID

Obtain your Vonage API Application id. You can access the existing applications in the dashboard. If you don't have an application already you can create a new application via Vonage CLI.

Get a Firebase Project ID

Get your Firebase project id from the Firebase console. Navigate to Firebase console -> Project settings -> General.

Displaying the project settings location
Displaying the project settings location

Displaying the project ID location
Displaying the project ID location

Get a Firebase Server Key

Get your Firebase Server Key from the Firebase console. Navigate to Firebase console -> Project settings -> Service accounts and generate a new private key.

Displaying the project settings location
Displaying the project settings location

Displaying the server key location
Displaying the server key location

Connect your Vonage Application to Firebase

You connect Vonage with your Firebase application by making a POST request. You can to this request using the Upload Tool or making a POST request directly.

Using the Upload Tool

The Android Push Certificate Uploading Tool, available on GitHub, allows you to upload with a user interface.

To use the tool you will need to run it locally or deploy it. You can follow the the instructions in the GitHub project's README.

Once you have the tool running, enter your Vonage Application ID, private key file, Firebase project ID, and Firebase server key then click upload. The status of your upload will be shown on the page once it is complete:

Android Push Certificate Uploading Tool success
Android Push Certificate Uploading Tool success

Using the Terminal

To connect the Vonage backend push service with the Firebase application you need to make a single POST request. Before making request you will have to generate Vonage developer JWT (above upload tool generates this JWT under the hood).

NOTE JWTs are used to authenticate a user into the Client SDK.

To generate a Vonage admin JWT run the following command. Remember to replace the VONAGE_APP_ID with ID of your Vonage application:

vonage jwt --key_file=./private.key --acl='{"paths":{"/*/users/**":{},"/*/conversations/**":{},"/*/sessions/**":{},"/*/devices/**":{},"/*/image/**":{},"/*/media/**":{},"/*/applications/**":{},"/*/push/**":{},"/*/knocking/**":{},"/*/legs/**":{}}}' --app_id=VONAGE_APP_ID

NOTE An admin JWT is a JWT without a sub claim. More details on how to generate a JWT can be found in the setup guide.

Fill VONAGE_APP_ID, VONAGE_JWT, FIREBASE_PROJECT_ID and FIREBASE_SERVER_KEY with previously obtained values and run the below command to send the request:

VONAGE_APP_ID=
VONAGE_JWT=
FIREBASE_PROJECT_ID=
FIREBASE_SERVER_KEY=

curl -v -X PUT \
   -H "Authorization: Bearer $VONAGE_JWT" \
   -H "Content-Type: application/json" \
   -d "{\"token\":\"$FIREBASE_SERVER_KEY\", \"projectId\":\"$FIREBASE_PROJECT_ID\"}" \
   https://api.nexmo.com/v1/applications/$VONAGE_APP_ID/push_tokens/android  

NOTE There is no validation at this endpoint. The 200 return code means that Vonage got the data and stored it but hasn't checked that values are valid.

If all the values are correct you should see 200 response code in the terminal.

Set up Firebase project for your Android application

In order to enable push notifications for your Android application, you need to configure your Android application.

Configure your Android project

Let's start with configuring your Android project with the required dependencies.

Add the Client SDK dependency

Add the Client SDK to your project.

Add Firebase Configuration to your App

Before we set up the push notification-specific configuration there are a few general steps you need to take to set up Firebase within your app.

NOTE that you can skip this step if your application is already using other Firebase products.

From your Firebase project click on the "add Android app" option:

Add App getting started screen with options for ios, android, web, unity and flutter
Add App getting started screen with options for ios, android, web, unity and flutter

Fill in the displayed form to register your application with the Firebase project

Form to register your application with your Firebase project, package name is required.
Form to register your application with your Firebase project, package name is required.

You will then be presented with a "Download google-services.json" button, click this and download the file.

Now switch over to the Project view in Android Studio to see your project root directory.

Move the google-services.json file you downloaded into your Android app module root directory.

Android studio with project view selected and the google-service.json added in the app module directory
Android studio with project view selected and the google-service.json added in the app module directory

Finally, you need to add the Google services plugin, which will load the google-services.json file. Modify your project-level build.gradle file to include this:

buildscript {
    repositories {
        // Check that you have the following line (if not, add it):
        google()
    }

    dependencies {
        // Add this line
        classpath("com.google.gms:google-services:4.3.10")
    }
}

allprojects {
    repositories {
        // Check that you have the following line (if not, add it):
        google()
    }
}
buildscript {
  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository

  }
  dependencies {
    // Add this line
    classpath 'com.google.gms:google-services:4.3.10'

  }
}

allprojects {
  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository

  }
}

And in your App-level build.gradle implement the base Firebase BoM

plugins {
    id("com.android.application")
    id("com.google.gms.google-services")
}

dependencies {
  // Import the Firebase BoM
  implementation platform('com.google.firebase:firebase-bom:30.1.0')
}
apply plugin: 'com.android.application'

// Add this line
apply plugin: 'com.google.gms.google-services'


dependencies {
  // Import the Firebase BoM
  implementation platform('com.google.firebase:firebase-bom:30.1.0')
}

Add Firebase Cloud Messaging dependency

In your IDE, in your app level build.gradle file (usually app/build.gradle), add the firebase-messaging dependency:

dependencies{
    implementation("com.google.firebase:firebase-messaging:21.0.1")
}
dependencies{
    implementation 'com.google.firebase:firebase-messaging:21.0.1'
}

NOTE: The latest version number can be found on the Firebase website.

Implement a custom service class to receive push notifications

If you do not have one already, create a class (service) that extends FirebaseMessagingService.

In order for Vonage to be able to send push notifications to a device, the Vonage server has to know the device's InstanceID.

In your class that extends FirebaseMessagingService, override onNewToken() method and update the NexmoClient by passing a new token:

class MyFirebaseMessagingService: FirebaseMessagingService() {

    // We can retrieve client instance only if it has been already initialized
    // NexmoClient.Builder().build(context)
    private val client = NexmoClient.get()

    override fun onNewToken(token: String) {
        super.onNewToken(token)

        client.enablePushNotifications(token, object: NexmoRequestListener<Void> {
            override fun onSuccess(p0: Void?) { }

            override fun onError(apiError: NexmoApiError) {}
        })
    }
}
public class MyFirebaseMessagingService extends FirebaseMessagingService {

    // We can retrieve client instance only if it has been already initialized
    // new NexmoClient.Builder().build(context)
    private NexmoClient client = NexmoClient.get();

    @Override
    public void onNewToken(@NonNull String token) {
        super.onNewToken(token);

        client.enablePushNotifications(token, new NexmoRequestListener<Void>() {
            @Override
            public void onSuccess(@Nullable Void p0) {}

            @Override
            public void onError(@NonNull NexmoApiError nexmoApiError) {}
        });
    }
}

Make sure your service is declared in your AndroidManifest.xml (typically app/src/main/AndroidManifest.xml) by adding service tag inside application tag:

Arrow showing the location of the manifest file
Arrow showing the location of the manifest file

<service android:name=".MyFirebaseMessagingService"
         android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

Receive push notifications

Push notifications are received in your implementation of MyFirebaseMessagingService, on onMessageReceived() method. You can use NexmoClient.isNexmoPushNotification(message.data)) to determine if the message is sent from Vonage server.

Use processPushNotification(message.data, listener) to process the data received from Firebase Cloud Messaging (FCM) into a ready to use object:

class MyFirebaseMessagingService : FirebaseMessagingService() {

    // We can retrieve client instance only if it has been already initialized
    // in Application class or Activity:
    // NexmoClient.Builder().build(context)
    private val client = NexmoClient.get()

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        //...   
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // determine if the message is sent from Nexmo server
        if (NexmoClient.isNexmoPushNotification(remoteMessage.data)) {
            client.processNexmoPush(remoteMessage.data, object : NexmoPushEventListener {
                override fun onIncomingCall(call: NexmoCall?) {
                    Log.d("TAG", "FirebaseMessage:onIncomingCall() with: $call")
                }

                override fun onNewEvent(event: NexmoEvent?) {
                    Log.d("TAG", "FirebaseMessage:onNewEvent() with: $event")
                }

                override fun onError(apiError: NexmoApiError?) {
                    Log.d("TAG", "FirebaseMessage:onError() with: $apiError")
                }
            })
        }
    }
}
public class MyFirebaseMessagingService extends FirebaseMessagingService {

    // We can retrieve client instance only if it has been already initialized
    // in Application class or Activity:
    // new NexmoClient.Builder().build(context)
    private NexmoClient client = NexmoClient.get();

    @Override
    public void onNewToken(@NonNull String token) {
        super.onNewToken(token);

        //...
    }

    @Override
    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        // determine if the message is sent from Nexmo server
        if (NexmoClient.isNexmoPushNotification(remoteMessage.getData())) {
            client.processNexmoPush(remoteMessage.getData(), new NexmoPushEventListener() {
                @Override
                public void onIncomingCall(NexmoCall call) {
                    Log.d("TAG", "FirebaseMessage:onIncomingCall() with: " + call);
                }

                @Override
                public void onNewEvent(NexmoEvent event) {
                    Log.d("TAG", "FirebaseMessage:onNewEvent() with: " + event);
                }

                @Override
                public void onError(NexmoApiError apiError) {
                    Log.d("TAG", "FirebaseMessage:onError() with: " + apiError);
                }
            });
        }
    }
}

NOTE: In order to process the push notification and apply any methods on the NexmoCall object (for example, answer, hangup, and so on), the NexmoClient has to be initialized and the user has to be logged in.

Configure Push Notification TTL

You can configure the time-to-live (TTL) for push notifications, this will stop stale push notifications being delivered to a device after they are no longer relevant. The default value is 120 seconds. To set the TTL, configure the NexmoClient:

// TTL value is in seconds, TTL ranges from 0 to 300.
val nexmoClient = NexmoClient.Builder()
    .pushNotificationTTL(30)
    .build(context)
// TTL value is in seconds, TTL ranges from 0 to 300.
NexmoClient nexmoClient = new NexmoClient.Builder()
    .pushNotificationTTL(30)
    .build(this);

Changes to the NexmoClient configuration must be done before the first call to NexmoClient().get().

Putting it all together

Now you can test your push notification setup by calling your app's user. The incoming call will trigger onIncomingCall callback as shown above. If you have registered an incoming call listener with NexmoClient.addIncomingCallListener elsewhere in your Android app, this listener will take precedence over onIncomingCallon the NexmoPushEventListener.

Conclusion

In this guide you have seen how to set up push notifications. You can also find a sample project that uses the ConnectionService API and push notifications to handle an incoming call using the Android system UI on GitHub.

Overview

On incoming events such as a new message, or an incoming call, the user often expects to receive a push notification, if the app is not active.

There are two types of push notifications that you can use:

  • VoIP push (PushKit) - the better fit for applications that use Vonage In-App Voice functionality. (Jump to section)

There is a VoIP push sample project on GitHub and an accompanying blog post which shows you how to integrate CallKit into an existing Vonage Client SDK project.

Create a push certificate

Apple Push Notifications service (APNs) uses certificate-based authentication to secure the connections between APNs and Vonage servers. So you will need to create a certificate and upload it to the Vonage Servers.

Adding a push notification capability

To use push notifications you are required to add the push notification capability to your Xcode project. To do this select your target and select Signing & Capabilities:

Signing & Capabilities
Signing & Capabilities

Then select add capability and add the Push Notifications capability:

Add push capability
Add push capability

If Xcode is automatically managing the signing of your app it will update the provisioning profile linked to your Bundle Identifier to include the capability.

Generating a push certificate

To generate a push certificate you will need to log in to your Apple developer account and head to the Certificates, Identifiers & Profiles page and add a new certificate:

Add certificate button
Add certificate button

Choose an Apple Push Notification service SSL (Sandbox & Production) and continue.

Apple Push Notification service option
Apple Push Notification service option

If the Apple Push Notification service option is not available to you, you will need to ask an admin for your Apple developer account to complete this step for you.

You will now need to choose the App ID for the app that you want to add push notifications to and continue. If your app is not listed you will have to create an App ID. Xcode can do this for you if it automatically if it manages your signing for you, otherwise you can create a new App ID on the Certificates, Identifiers & Profiles page under Identifiers. Make sure to select the push notifications capability when doing so.

You will be prompted to upload a Certificate Signing Request (CSR). You can follow the instructions on Apple's help website to create a CSR on your Mac. Once the CSR is uploaded you will be able to download the certificate. Double click the .cer file to install it in Keychain Access.

To get the push certificate in the format that is needed by the Vonage servers you will need to export it. Locate your Apple Push Notification service certificate in Keychain Access and right-click to export it. Name the export applecert and select .p12 as the format:

Keychain export
Keychain export

You can find more details about connecting to APNs in Apple's official documentation.

Upload your certificate

You upload your certificate to the Vonage servers by making a POST request. The iOS Push Certificate Uploading Tool, available on GitHub, does so with a web interface. Either of the following methods needs your Vonage Application ID. It can be obtained from the dashboard.

Using the Upload Tool

To use the tool you will need to run it locally or deploy it. You can follow the the instructions in the GitHub project's README. You will also need the private key for your Vonage Application.

Once you have the tool running, enter your Vonage Application ID, private key file, and certificate file and click upload. The status of your upload will be shown on the page once it is complete:

iOS Push Certificate Uploading Tool success
iOS Push Certificate Uploading Tool success

Using the Terminal

In addition to your Vonage Application ID to upload using the terminal, you will also need a jwt_dev. Which is a jwt without a sub claim. More details on how to generate a JWT can be found in the setup guide.

Then run the following Curl command, replacing jwt_dev, applecert.p12, app_id with your values:

hexdump -ve '1/1 "%.2x"' < applecert.p12 > applecert.pfx.hex
hextoken=`cat applecert.pfx.hex`

curl -v -X PUT \
   -H "Authorization: Bearer $jwt_dev" \
   -H "Content-Type: application/json" \
   -d "{\"token\":\"$hextoken\"}" \
   https://api.nexmo.com/v1/applications/$app_id/push_tokens/ios

NOTE There is no validation at this endpoint. The 200 return code means that Vonage got the data and stored it but hasn't checked that values are valid.

Integrate VoIP push notifications in your application

VoIP push notifications are suitable for VoIP apps. Among other benefits, it allows you to receive notifications even when the app is terminated. After you have uploaded your certificate to Vonage, integrate VoIP push in your app by following these steps:

1. Enable VoIP Background Mode for your app

Similar to the process for adding the push notifications capability earlier, in Xcode, under your target, open Capabilities and select Background Modes. Once the capability is added tick the "Voice over IP" option:

Background modes selected
Background modes selected

When using VoIP push notifications, you have to use the CallKit framework. Link it to your project by adding it under Frameworks, Libraries, and Embedded Content under General:

Linking CallKit framework
Linking CallKit framework

3. Import PushKit, adopt PKPushRegistryDelegate, and sign up to VoIP notifications

Add a voipRegistry property:

let voipRegistry = PKPushRegistry(queue: nil)

and set it up:

func registerForVoIPPushes() {
    self.voipRegistry.delegate = self
    self.voipRegistry.desiredPushTypes = [PKPushType.voIP]
}

Add a voipRegistry property:

@property PKPushRegistry* voipRegistry;

and set it up:

- (void) registerForVoIPPushes {
    self.voipRegistry = [[PKPushRegistry alloc] initWithQueue:nil];
    self.voipRegistry.delegate = self;

    // Initiate registration.
    self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
}

4. Enable VoIP push notifications through a logged in NXMClient

NXMClient.shared.enablePushNotifications(withPushKitToken: pushKitToken, 
        userNotificationToken: nil, isSandbox: true) { (error) in
    // code
}
[NXMClient.shared enablePushNotificationsWithPushKitToken:pushKitToken 
        userNotificationToken:userNotificationToken isSandbox:YES 
        completionHandler:^(NSError * _Nullable error) {
    // code 
}];
  • 'isSandbox' is YES/true for an app using the Apple sandbox push servers and NO/false for an app using the Apple production push servers.

  • 'pushKitToken' is the token received in pushRegistry(_:didUpdate:for:).

5. Implement the following delegate method and add the code to handle an incoming VoIP push notification

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith 
        payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    if(NXMClient.shared.isNexmoPush(userInfo: payload.dictionaryPayload)) {
        guard let pushPayload = NXMClient.shared.processNexmoPushPayload(payload.dictionaryPayload) else {
            NSLog("Not a Nexmo push notification")
            return
        }
    }
}
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload 
        forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
    if ([NXMClient.shared isNexmoPushWithUserInfo:payload.dictionaryPayload]) {
        NXMPushPayload *pushPayload = [NXMClient.shared processNexmoPushPayload:payload.dictionaryPayload];
        if (!pushPayload){
           NSLog(@"Not a nexmo push");
           return;
        };
    }
}

The pushPayload contains information about the incoming call that you can use to report the call with CallKit. You can view a sample of the JSON contained in the push payload.

For the SDK to enable push notifications and to process the push notifications properly NXMClient should be logged in. You can store both the push token and push payload and handle them when the client successfully connects. For an example of this, view the blog post on using push notifications with CallKit and its accompanying sample project.

Integrate regular push notifications in your application

Regular push notifications are suitable for messaging apps, to integrate them in your app, follow these steps:

1. Enable Remote Notifications for your app

Similar to the process for adding the push notifications capability earlier, in Xcode, under your target, open Capabilities and select Background Modes. Once the capability is added tick the "Remote Notifications" option:

Remote Notifications selected
Remote Notifications selected

2. Import UserNotifications and register for push notifications

Import UserNotifications:

import UserNotifications

Then request permissions and register for remote notifications:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        registerForPushNotificationsIfNeeded()
        return true
    }

    func registerForPushNotificationsIfNeeded() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
            print("Permission granted: \(granted)")
            if granted {
                self?.getNotificationSettings()
            }
        }
    }

    func getNotificationSettings() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            print("Notification settings: \(settings)")
            guard settings.authorizationStatus == .authorized else { return }
            DispatchQueue.main.async {
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
    }
 ...
}

Import UserNotifications:

#import <UserNotifications/UserNotifications.h>

Then request permissions and register for remote notifications:

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [self registerForPushNotificationsIfNeeded];
    return YES;


}

- (void)registerForPushNotificationsIfNeeded {
    UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionSound + UNAuthorizationOptionBadge;

    [UNUserNotificationCenter.currentNotificationCenter requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError * _Nullable error) {
        if (granted) {
            [self getNotificationSettings];
        }
    }];
}

- (void)getNotificationSettings {
    [UNUserNotificationCenter.currentNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
        if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [UIApplication.sharedApplication registerForRemoteNotifications];
            });
        }
    }];
}

...

@end

3. Enable push notifications through a logged in NXMClient

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    NXMClient.shared.enablePushNotifications(withPushKitToken: nil, userNotificationToken: deviceToken, isSandbox: true, completionHandler: nil)
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    [NXMClient.shared enablePushNotificationsWithPushKitToken:nil userNotificationToken:deviceToken isSandbox:true completionHandler:nil];
}

4. Implement the following delegate method and add the code to handle an incoming push notification

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    if NXMClient.shared.isNexmoPush(userInfo: userInfo) {
        if UIApplication.shared.applicationState != .active {
            // create and add notification
        } else {
            // show in app banner etc.
        }
        completionHandler(UIBackgroundFetchResult.newData)
    } else {
        completionHandler(UIBackgroundFetchResult.failed)
    }
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    if ([NXMClient.shared isNexmoPushWithUserInfo:userInfo]) {
        if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) {
            // create and add notification
        } else {
            // show in app banner etc.
        }
        completionHandler(UIBackgroundFetchResultNewData);
    } else {
        completionHandler(UIBackgroundFetchResultFailed);
    }
}

The userInfo contains information about the incoming message that you can use to create a notification with UNUserNotificationCenter. You can view a sample of the JSON contained in the user info.

Configure Push Notification TTL

You can configure the time-to-live (TTL) for push notifications, this will stop stale push notifications being delivered to a device after they are no longer relevant. The default value is 120 seconds. To set the TTL, configure the NXMClient:

let config = NXMClientConfig()
// TTL value is in seconds, TTL ranges from 0 to 300.
config.pushNotificationTTL = 30
NXMClient.setConfiguration(config)
NXMClientConfig *config = [[NXMClientConfig alloc] init];
// TTL value is in seconds, TTL ranges from 0 to 300.
config.pushNotificationTTL = 30;
[NXMClient setConfiguration:config];

Changes to the NXMClient configuration must be done before the first call to NXMClient.shared.

Conclusion

In this guide you have seen how to set up push notifications. You can find the VoIP push sample project on GitHub and learn more about CallKit on developer.apple.com. More information on UserNotifications is also available on developer.apple.com