Use the JavaScript SDK with cross-platform apps

A tutorial for adding the JavaScript SDK code to your app to allow your app users to receive push notifications from your Dotdigital account

The JavaScript SDK is for cross-platform apps. It works differently from our native SDKs, because it runs inside a JavaScript framework and reaches the device APIs through that framework rather than directly.

This means your app must do two things that the native SDKs do for you:

  1. Get the native device push token, which the SDK calls the registration ID. Use your framework's push plugin to do this, then pass the token to the JavaScript SDK.
  2. Open any deep link that arrives with a push message. The JavaScript SDK reports the tap for you, but it does not open the link.
Diagram: the JavaScript framework passes the registration ID from the device to Dotdigital Marketing through the JavaScript SDK, and Dotdigital Marketing sends push messages back to the app.

To set up push notifications for cross-platform apps, complete the following tasks:

  1. Install the JavaScript SDK
  2. Configure the JavaScript SDK
  3. Initialise the JavaScript SDK
  4. Ask permission to send notifications
  5. Register the push token for the app
  6. Create an Android notification channel
  7. Handle push notifications
  8. Handle deep links and link tracking
  9. Optional: Handle silent and background data pushes
❗️

Your app needs a native wrapper, because this SDK does not support web push

The JavaScript SDK sends push notifications through Apple's APNS and Google's FCM. Both services need a native device token, so your JavaScript code must run somewhere that can reach the native push APIs.

This works when your web app runs inside a native wrapper that provides a JavaScript-to-native bridge and a push plugin, such as Cordova, Capacitor, or React Native. A progressive web app (PWA) packaged this way gets a native device token in the same way any other cross-platform app does, so push notifications work normally.

This does not work when your web app runs in a browser, or when it is installed to the home screen and still runs in the browser engine. In those cases there is no native push plugin, so there is no native token to send us. Dotdigital Marketing does not support browser web push, which uses a service worker and the Web Push protocol instead of APNS or FCM tokens.

🚧

Check that your wrapper exposes a native push plugin

Not every native wrapper gives your JavaScript access to the native push APIs. An Android Trusted Web Activity (TWA), for example, runs your site in the browser engine, so its notifications use web push rather than an FCM token that your JavaScript can read.

Before you start, confirm that your wrapper has a push plugin that returns a native APNS or FCM device token. If it returns a web push subscription instead, you cannot use it with this SDK.

📘

How this SDK differs from the native SDKs

TaskNative Android and iOS SDKsJavaScript SDK
Ask the user for permissionThe SDK guide shows you the platform codeYour framework's push plugin asks for it
Get the device push tokenThe SDK collects itYour framework's push plugin collects it, then you pass it to the SDK
Detect the platformNot neededYou must detect it, because iOS and Android use different SDK methods
Create an Android notification channelNot needed for the default channelYour framework's push plugin creates it
Report a push tapThe SDK reports itThe SDK reports it, but only when you pass the message to it
Open a deep linkThe SDK opens itYour app or a Cordova plugin opens it
Display a foreground notificationYou build itYour framework's push plugin can build it, or you build it

Sample code

This tutorial shows each code sample in two forms: ES6 for projects that use modules, and Classical for projects that use the global COMAPI object. Use the form that matches your project. A full TypeScript sample is in the JavaScript SDK sample code in TypeScript section.

More sample apps are available below. These are basic examples of one way to integrate the JavaScript SDK. They are not production-ready solutions:

Install the JavaScript SDK

Install the JavaScript SDK from NPM:

npm install @comapi/sdk-js-foundation --save
📘

ES6 Promises

This SDK uses ES6 Promises, which have wide support in web containers and browsers. Depending on the browsers that you target, you may need a polyfill such as es6-shim.

If your project uses ES6 modules

Most projects use ES6 modules, for example projects built with Angular, Ionic, or React Native. Import the Foundation, ComapiConfig, and IAuthChallengeOptions modules:

import { Foundation, ComapiConfig, IAuthChallengeOptions } from "@comapi/sdk-js-foundation";

The NPM package is written in TypeScript and ships its own type definitions, so the ES6 examples in this guide are in TypeScript.

If your project uses classical JavaScript

If you do not use a module bundler, and you want a script that exposes a global object on your page, use the pre-built bundle in the package. Copy comapi-foundation.js or comapi-foundation.min.js from the dist folder of the installed package into the folder that your app serves scripts from, then include it:

<!-- Full version -->
<script src="scripts/comapi-foundation.js"></script>

<!-- Minified version -->
<script src="scripts/comapi-foundation.min.js"></script>

The bundle exposes a single COMAPI global object. The classical examples in this guide use it.

Safe list the API calls that the SDK makes

Cordova based apps restrict the URIs that the app pages can access. You must safe list the URIs that the SDK uses, so that the SDK can operate.

If you use cordova-plugin-whitelist, add this line to the config.xml file in your project:

<allow-navigation href="https://*.comapi.com/*" />

If you use Content-Security-Policy tags, include this directive:

connect-src https://api.comapi.com:*

For example:

<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;connect-src https://api.comapi.com:*">

Configure the JavaScript SDK

Before you configure the JavaScript SDK, you need:

  1. Create a ComapiConfig object:
var comapiConfig = new COMAPI.ComapiConfig();
  1. Pass your API space ID to the withApiSpace() method:
var comapiConfig = new COMAPI.ComapiConfig()
    .withApiSpace(appConfig.apiSpaceId);
  1. Pass the function that creates a JWT to the withAuthChallenge() method:
var comapiConfig = new COMAPI.ComapiConfig()
    .withApiSpace(appConfig.apiSpaceId)
    .withAuthChallenge(challengeHandler);

Setting the log level

Use withLogLevel() to control how much the SDK logs. The available levels are LogLevels.None, LogLevels.Error, LogLevels.Warn, and LogLevels.Debug.

var comapiConfig = new COMAPI.ComapiConfig()
    .withApiSpace(appConfig.apiSpaceId)
    .withAuthChallenge(challengeHandler)
    .withLogLevel(COMAPI.LogLevels.Warn);
🔒

Privacy note

Debug logs can include request and response detail. Do not ship a release build at Debug level.

Initialise the JavaScript SDK

After you configure the SDK, pass the comapiConfig object to the initialise() method.

This method creates a valid session. It calls your JWT function, then uses the JWT to create the user's profile ID, which is a profileId string.

When you stop a session and start it again, the SDK requests a new JWT and uses it to create a new profile ID.

The SDK creates a new session when you call initialise() in these cases:

  • The user uninstalls the app, then reinstalls it.
  • The user clears all of the app's data.
  • You call the endSession() method. Call this method only when you want to stop the app receiving push notifications, or when you want to change the app user.

Your app needs a valid session before it can add an email address or a push token to the user's profile.

COMAPI.Foundation.initialise(comapiConfig)
    .then(function (sdk) {
        console.log("Foundation interface created", sdk);
    })
    .catch(function (error) {
        console.error("Failed to initialise", error);
    });
📘

initialise() and initialiseShared()

Foundation.initialise() returns the SDK instance, and you store it yourself. Foundation.initialiseShared() also stores the instance inside the SDK, so that you can retrieve it elsewhere in your app. Both methods return a Promise.

Next, get a native device push token and pass it to the SDK. The next section covers this.

Ask permission to send notifications

App users must give permission before your app can display push notifications. The requirement is different on each platform, and your framework's push plugin asks for it.

PlatformRequirement
iOS, all versionsThe user must accept a permission prompt.
Android 12 and earlierNo runtime permission is needed.
Android 13 and later (API 33)The user must grant the POST_NOTIFICATIONS runtime permission.
❗️

Android 13 drops every notification without the runtime permission

On Android 13 and later, the operating system discards every notification if the user has not granted POST_NOTIFICATIONS. It reports no error to your app.

Your token registration still succeeds, and your contact still appears in Dotdigital Marketing, so this problem looks like a delivery fault rather than a permission fault. Always request the permission, then test on an Android 13 or later device.

With Capacitor

The @capacitor/push-notifications plugin handles both platforms with one call. Check the current permission first, request it only when needed, then register.

import { PushNotifications } from '@capacitor/push-notifications';

async function registerForPush() {
    let status = await PushNotifications.checkPermissions();

    if (status.receive === 'prompt') {
        status = await PushNotifications.requestPermissions();
    }

    if (status.receive !== 'granted') {
        // The user declined. Do not register.
        return;
    }

    // This triggers the 'registration' listener with the native device token.
    await PushNotifications.register();
}

With Cordova

On iOS, the push plugin shows the permission prompt when you call init() with the alert, badge, or sound options set.

On Android 13 and later, request POST_NOTIFICATIONS yourself. Your push plugin may not do this for you, so check the documentation for the plugin version that you use. If it does not, use a permissions plugin such as cordova-plugin-android-permissions:

function ensureAndroidNotificationPermission(onComplete) {
    // Only Android 13 (API 33) and later need this permission.
    if (!cordova.plugins || !cordova.plugins.permissions) {
        onComplete();
        return;
    }

    var permissions = cordova.plugins.permissions;
    var permission = permissions.POST_NOTIFICATIONS;

    permissions.checkPermission(permission, function (status) {
        if (status.hasPermission) {
            onComplete();
        } else {
            permissions.requestPermission(permission, onComplete, onComplete);
        }
    }, onComplete);
}

Get a native device token and register it

🚧

Only users that have both an email address and a push token are created in Dotdigital Marketing

Read the Register your app users for push page to understand the requirements for syncing your app users with Dotdigital Marketing. Dotdigital Marketing syncs only the users who have both an email address and a push token. Only these users can receive push messages.

Many frameworks can get the native push tokens that Dotdigital Marketing needs. These include React Native, Cordova with the PhoneGap push plugin, and Ionic Capacitor.

This tutorial uses the Cordova framework with the PhoneGap plugin. Any framework that you choose must be able to get a native device token, which this SDK calls the registrationId.

Before you start:

  • For iOS, you need your app ID's bundle ID. This must match the value of the id attribute in the <widget> element of your config.xml file.
  • For Android, you need the package name that you entered in your push notification profile.
🚧

Make sure your bundle ID and package name are correct

Your bundle ID for iOS and your package name for Google FCM must match the values configured in the respective developer portal, and in your push notification profile.

Why you must store the token

The SDK needs an active session before it can register a push token. Both setAPNSPushDetails() and setFCMPushDetails() call ensureSession() internally, so they fail if no session exists.

Your push plugin gets the token asynchronously, and it can deliver the token before the SDK has a session. This creates a race condition between two events that you do not control.

To avoid the race, split the two tasks:

  1. When your push plugin delivers a token, store it, then try to register it.
  2. Each time your app starts and the SDK has a session, try to register the stored token again.

The examples below store the token in localStorage for this reason.

🚧

Push tokens change, so register on every token event

Do not treat the token as a value that you collect once. FCM tokens rotate, and APNS tokens change when the user restores a backup or reinstalls your app. Your plugin fires its registration event again each time this happens.

Register the token on every registration event, not only the first. Also register the stored token again after the SDK starts a new session, because the SDK links the token to the current session.

Choose the correct APNS environment

The SDK sends the APNS environment with the token. You must pass the value that matches your app build type, or APNS does not deliver your push messages.

Build typeEnvironment parameter
Development buildsEnvironment.development
Ad hoc buildsEnvironment.production
TestFlight buildsEnvironment.production
App Store buildsEnvironment.production
🚧

Set the environment from your build configuration

Do not hard code Environment.development. A development token is not valid in the production environment, and APNS returns no error when the environments do not match. Your App Store users then receive no push messages. Read the value from your build configuration instead.

Format of the APNS token

🚧

Ensure your APNS token is formatted correctly

The SDK expects the APNS push token as a 64 character hexadecimal string. Most framework push plugins do this for you. A correct token looks like this:

02df25c845d460bcdad7802d2af6fc1dfce97283bf75cc993eb6dca835ea2e2f

If your token does not look like the example, it is likely that the raw bytes returned by iOS were cast straight to a string instead of to a hexadecimal string.

Get a registration ID with Cordova and the PhoneGap plugin

Dotdigital Marketing can send push notifications only after you pass the registrationId to the JavaScript SDK.

Getting the registrationId is an asynchronous task that runs after the Cordova deviceready event. In the example below, an event handler stores the native push token when the push plugin receives one, then registers it with the JavaScript SDK.

Set the registrationId at both of these times, where possible:

  • When the platform.ready event fires, which happens each time the app opens.
  • Whenever your push library fires an event to say that it has acquired a registrationId.

The JavaScript SDK uses a different method for each operating system. Use the Cordova device plugin to find out which platform the user is on.

import { Environment } from "@comapi/sdk-js-foundation";
import { Push } from 'ionic-native';

const NATIVE_PUSH_TOKEN = "app.nativePushToken";

// Set the APNS environment from your build configuration, not by hand.
const APNS_ENVIRONMENT = isProductionBuild ? Environment.production : Environment.development;

// Platform ready event handler
platform.ready().then(() => {
    // Initialise the Ionic push plugin, or the equivalent for your framework.
    let push = Push.init({
        // For Android apps
        android: { senderID: 'Your_Project_ID' },
        // For iOS apps
        ios: {
            alert: "true",
            badge: true,
            sound: 'false'
        }
    });

    // Handle the native push tokens that arrive asynchronously.
    push.on('registration', (data) => {
        console.log("Got a native push token: ", data.registrationId);

        // Store the native token, so that you can reuse it at each launch.
        localStorage.setItem(NATIVE_PUSH_TOKEN, data.registrationId);

        // Register the push token with the SDK.
        registerNativeToken();
    });

    // Error handling for the push plugin
    push.on('error', (error) => {
        console.error('Error with Push plugin', error);
    });

    // Your own push notification handling, if you need it.
    push.on('notification', (data) => {
        console.log("got a push notification", data);
    });

    // Try to register a native push token.
    registerNativeToken();
});

function registerNativeToken() {
    // Retrieve the stored native push token.
    let registrationId = localStorage.getItem(NATIVE_PUSH_TOKEN);

    // Skip if the registrationId has not arrived yet.
    if (!registrationId) {
        console.log("No native push token available yet");
        return;
    }

    // Call a different method for each operating system.
    if (platform.is('ios')) {
        // The bundle ID must match the value in your push notification profile
        // in Dotdigital Marketing, and the environment must match your build type.
        sdk.device.setAPNSPushDetails("<Bundle Id>", APNS_ENVIRONMENT, registrationId)
            .then(result => {
                console.log("setAPNSPushDetails() succeeded", result);
            })
            .catch(error => {
                console.error("setAPNSPushDetails() failed", error);
            });

    } else if (platform.is('android')) {
        // The package name must match the value in your push notification profile
        // in Dotdigital Marketing.
        sdk.device.setFCMPushDetails("<Package Name>", registrationId)
            .then(result => {
                console.log("setFCMPushDetails() succeeded", result);
            })
            .catch(error => {
                console.error("setFCMPushDetails() failed", error);
            });
    }
}
const NATIVE_PUSH_TOKEN = "app.nativePushToken";

// Set the APNS environment from your build configuration, not by hand.
var APNS_ENVIRONMENT = isProductionBuild ? COMAPI.Environment.production : COMAPI.Environment.development;

// Platform ready event handler
platform.ready().then(function () {
    // Initialise the push plugin, or the equivalent for your framework.
    var push = PushNotification.init({
        // For Android apps
        android: { senderID: 'Your_Project_ID' },
        // For iOS apps
        ios: {
            alert: "true",
            badge: true,
            sound: 'false'
        }
    });

    // Handle the native push tokens that arrive asynchronously.
    push.on('registration', function (data) {
        console.log("Got a native push token: ", data.registrationId);

        // Store the native token, so that you can reuse it at each launch.
        localStorage.setItem(NATIVE_PUSH_TOKEN, data.registrationId);

        // Register the push token with the SDK.
        registerNativeToken();
    });

    // Error handling for the push plugin
    push.on('error', function (error) {
        console.error('Error with Push plugin', error);
    });

    // Your own push notification handling, if you need it.
    push.on('notification', function (data) {
        console.log("got a push notification", data);
    });

    // Try to register a native push token.
    registerNativeToken();
});

function registerNativeToken() {
    // Retrieve the stored native push token.
    var registrationId = localStorage.getItem(NATIVE_PUSH_TOKEN);

    // Skip if the registrationId has not arrived yet.
    if (!registrationId) {
        console.log("No native push token available yet");
        return;
    }

    // Call a different method for each operating system.
    if (platform.is('ios')) {
        sdk.device.setAPNSPushDetails("<Bundle Id>", APNS_ENVIRONMENT, registrationId)
            .then(function (result) {
                console.log("setAPNSPushDetails() succeeded", result);
            })
            .catch(function (error) {
                console.error("setAPNSPushDetails() failed", error);
            });

    } else if (platform.is('android')) {
        sdk.device.setFCMPushDetails("<Package Name>", registrationId)
            .then(function (result) {
                console.log("setFCMPushDetails() succeeded", result);
            })
            .catch(function (error) {
                console.error("setFCMPushDetails() failed", error);
            });
    }
}
📘

Removing a push token

To stop a device receiving push messages without ending the session, call sdk.device.removePushDetails().

Get a registration ID with Capacitor

Capacitor is the supported successor to Cordova in the Ionic ecosystem. The @capacitor/push-notifications plugin delivers the native token in the registration listener.

import { PushNotifications } from '@capacitor/push-notifications';
import { Capacitor } from '@capacitor/core';
import { Environment } from '@comapi/sdk-js-foundation';

const NATIVE_PUSH_TOKEN = 'app.nativePushToken';

// Set the APNS environment from your build configuration, not by hand.
const APNS_ENVIRONMENT = isProductionBuild ? Environment.production : Environment.development;

// token.value is the native APNS or FCM token, already formatted correctly.
PushNotifications.addListener('registration', (token) => {
    localStorage.setItem(NATIVE_PUSH_TOKEN, token.value);
    registerNativeToken();
});

PushNotifications.addListener('registrationError', (error) => {
    console.error('Push registration failed', error);
});

async function registerNativeToken() {
    const registrationId = localStorage.getItem(NATIVE_PUSH_TOKEN);
    if (!registrationId) {
        return;
    }

    try {
        if (Capacitor.getPlatform() === 'ios') {
            await sdk.device.setAPNSPushDetails('<Bundle Id>', APNS_ENVIRONMENT, registrationId);
        } else if (Capacitor.getPlatform() === 'android') {
            await sdk.device.setFCMPushDetails('<Package Name>', registrationId);
        }
    } catch (error) {
        console.error('Failed to register push token with the SDK', error);
    }
}

Capacitor delivers your custom payload keys under the data property of the notification. The SDK looks for dd_deepLink there, so pass the whole notification object to handlePushAndGetMessageDetails():

PushNotifications.addListener('pushNotificationActionPerformed', (action) => {
    // Pass the whole notification, not action.notification.data.
    sdk.handlePushAndGetMessageDetails(action.notification)
        .then((details) => {
            if (details.ddOriginated && details.url) {
                // Report done. Your app must open the link itself.
            }
        })
        .catch((error) => console.error(error));
});

Get a registration ID with React Native and Expo

React Native apps usually get the token from a Firebase messaging library. Expo apps use expo-notifications.

❗️

Expo apps must send the native token, not the Expo push token

expo-notifications can return two different tokens:

  • getExpoPushTokenAsync() returns an Expo push token, which looks like ExponentPushToken[xxxxxxx]. This token works only with Expo's own push service. Do not send it to Dotdigital Marketing.
  • getDevicePushTokenAsync() returns the native APNS or FCM token. Send this token.

The SDK accepts any string, so it stores an Expo push token without complaint. The device then never receives a push message, and nothing reports an error.

To check which token you have, compare it against the format in Format of the APNS token.

import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';
import { Environment } from '@comapi/sdk-js-foundation';

const APNS_ENVIRONMENT = isProductionBuild ? Environment.production : Environment.development;

async function registerNativeToken() {
    const { status } = await Notifications.requestPermissionsAsync();
    if (status !== 'granted') {
        return;
    }

    // getDevicePushTokenAsync returns the native token. Do not use
    // getExpoPushTokenAsync here.
    const devicePushToken = await Notifications.getDevicePushTokenAsync();
    const registrationId = devicePushToken.data;

    try {
        if (Platform.OS === 'ios') {
            await sdk.device.setAPNSPushDetails('<Bundle Id>', APNS_ENVIRONMENT, registrationId);
        } else if (Platform.OS === 'android') {
            await sdk.device.setFCMPushDetails('<Package Name>', registrationId);
        }
    } catch (error) {
        console.error('Failed to register push token with the SDK', error);
    }
}

React Native Firebase delivers the payload under remoteMessage.data, which is one of the locations that the SDK checks. Pass the whole remoteMessage object to handlePushAndGetMessageDetails().

Create an Android notification channel

Android 8 and later requires a notification channel. Android does not display a notification that has no channel.

Your framework's push plugin creates the channel, so create it once when your app starts, before you register for push.

With Capacitor:

import { PushNotifications } from '@capacitor/push-notifications';
import { Capacitor } from '@capacitor/core';

async function createDefaultChannel() {
    if (Capacitor.getPlatform() !== 'android') {
        return;
    }

    await PushNotifications.createChannel({
        id: 'default',
        name: 'Default',
        description: 'Default push notifications',
        importance: 4,
        visibility: 1
    });
}

With Cordova, use the createChannel method of your push plugin. Check the documentation for the plugin version that you use, because the options differ between plugins.

📘

Match the channel ID to your push message

If you send a channel ID in your push payload, it must match a channel that your app has created. If the channel does not exist, Android does not display the notification.

JavaScript SDK sample code in TypeScript

// some app specific imports
import { AppSettings } from "../settings";
import { AuthService } from "./auth";

// Comapi class and interface imports
import { Foundation, ComapiConfig, IAuthChallengeOptions } from "@comapi/sdk-js-foundation"

export class ComapiService {

    public sdk: Foundation;

    private challengeHandler(options: IAuthChallengeOptions, answerAuthenticationChallenge) {
        this._authService.getToken(options.nonce)
            .then((token) => {
                answerAuthenticationChallenge(token);
            });
    }

    constructor(private _authService: AuthService) { }

    /**
     * Public method that encapsulates the initialisation of Comapi
     */
    public initialise(): Promise<Foundation> {

        return new Promise((resolve, reject) => {

            if (this._authService.isAuthenticated()) {

                let comapiConfig = new ComapiConfig()
                    .withApiSpace(AppSettings.APP_SPACE_ID)
                    // Bind this pointer, so that the authChallenge callback can
                    // access this._authService.
                    .withAuthChallenge(this.challengeHandler.bind(this));

                Foundation.initialise(comapiConfig)
                    .then((sdk) => {
                        this.sdk = sdk;
                        console.log("foundation interface created");
                        resolve(sdk);
                    })
                    .catch((error) => {
                        console.error("initialise failed", error);
                        reject(error);
                    });
            } else {
                reject("Not logged in");
            }
        });
    }
}

Handling push notifications

Reporting the push tap for analytics

Pass every received push message to handlePushAndGetMessageDetails(). Dotdigital Marketing produces push reporting from this call.

The method returns a Promise that resolves to an object with two fields:

  • url. Any deep link or URL in the message. This is undefined when the message contains no deep link.
  • ddOriginated. A Boolean that is true when Dotdigital Marketing sent the message.
push.on('notification', (data) => {
    sdk.handlePushAndGetMessageDetails(data)
        .then((details) => {
            if (!details.ddOriginated) {
                // Another system sent this message. Handle it your own way.
                return;
            }
            if (details.url) {
                // The tap is now reported. Your app must open the link itself.
                console.log("Deep link: ", details.url);
            }
        })
        .catch((error) => {
            console.error("Failed to handle push message", error);
        });
});
🚧

This method reports the tap, but it does not open the link

This is a difference from the native SDKs. On Android and iOS, the SDK opens the deep link for you. In JavaScript, handlePushAndGetMessageDetails() calls the tracking URL and returns the link to you. Your app must then open the link. See Deep link support and link tracking.

📘

Reporting only happens when the message contains a deep link

The SDK calls the tracking URL only when the payload contains a dd_deepLink object with a trackingUrl value. Dotdigital Marketing adds one only for the deep link and survey tap actions, so a message that sends custom data only records no tap, even when you call this method.

If you need click reporting for a push message, include a deep link in that message. See Send a deep link or custom data with a push message.

Detecting push messages from Dotdigital Marketing

Your app can receive push messages from more than one provider or system. Read the ddOriginated field from handlePushAndGetMessageDetails() to detect whether Dotdigital Marketing sent a message. This field is true if Dotdigital Marketing sent the message, and false if another system sent it.

The SDK reads this value from the dd_originated key at the top level of the push payload.

Where the SDK looks for the deep link

Push plugins wrap the payload in different ways. The SDK checks three locations for the dd_deepLink value, in this order:

  1. dd_deepLink at the top level of the payload.
  2. data.dd_deepLink.
  3. additionalData.dd_deepLink.

The SDK also accepts dd_deepLink as an object, or as a JSON string that it parses for you. If the string is malformed, the returned Promise rejects.

📘

Why the value can be a JSON string

An FCM data payload can hold only text values, so Dotdigital Marketing converts the dd_deepLink and dd_data objects to JSON strings before it sends them to Android devices. The SDK accepts both forms for this reason. If you read the payload yourself instead of using the SDK, parse the string before you use it.

🚧

Pass the whole message object to the SDK

Pass the complete message object that your push plugin gives you. Do not pass a sub-object of it. If you pass only part of the message, the SDK may not find the dd_deepLink or dd_originated values, and your reporting is then incomplete.

Handling custom data payloads

Dotdigital Marketing delivers custom JSON data with a push message, and your app decides what to do with it. Use this to build advanced push handling while you still send the message through Dotdigital Marketing.

The custom data arrives in the dd_data value of the push payload. Read it from the message object that your push plugin gives you. Like the deep link, your push plugin may place it at the top level, under data, or under additionalData, so check the shape of the payload that your framework delivers.

Your app is responsible for validating this data. Treat every value as untrusted input, and check that each key exists and has the type you expect before you use it.

To find out how to send custom data, and what you can put in it, see Send a deep link or custom data with a push message.

Displaying push notifications while the app is in the foreground

The operating system displays push notifications automatically only while the app is in the background. These notifications go to the system tray on Android, or to the Notification Center on iOS, and they start your app when the user taps them.

To display a push notification while the app is in the foreground, use the option that applies to your platform.

Android

On Android, you can let the push plugin display notifications automatically, or you can display them yourself.

To display notifications automatically, set the forceShow property of the android object to true in the init() method of the push plugin. The default value is false.

With forceShow set to true, the on('notification') callback runs only when the user taps the notification. You then decide what happens on the tap. The push notification is passed as an argument to the callback.

import { Push } from 'ionic-native';

let push = Push.init({
    // For Android apps
    android: {
        senderID: 'Your_Project_ID',
        forceShow: true
    }
});
const push = PushNotification.init({
    // For Android apps
    android: {
        senderID: 'Your_Project_ID',
        forceShow: true
    }
});

To display notifications yourself, leave forceShow set to false. The on('notification') callback then runs as soon as the message arrives, and the push notification is passed as an argument to it. You then build your own display.

iOS

On iOS, you must build your own display. The push notification is passed as an argument to the push.on('notification') callback.

Silent and background data pushes

A silent push delivers custom JSON data to your app without notifying the user. Use it to start an action in your app in the background.

Silent push support depends on your framework's push plugin, not on the JavaScript SDK. The SDK reads the payload that your plugin gives it, so your plugin must first wake your app and deliver the message.

What Dotdigital Marketing sends

The payload differs by platform, and neither form displays a notification:

PlatformWhat Dotdigital Marketing sends
Android, through FCMYour data values, with no notification object. FCM therefore displays nothing, and your plugin must hand the message to your code.
iOS, through APNSYour custom payload, plus content-available set to 1 in the aps dictionary. The push type is background and the priority is 5.

Dotdigital Marketing also omits the title and the message from a silent push, so put everything that your app needs into the data payload.

Setting up your app

To use silent pushes:

  1. On iOS, enable the Remote notifications background mode in your native project. In Xcode, select your app target, go to Signing & Capabilities, add Background Modes, then select Remote notifications.
  2. Check your plugin's background handler. Cordova and Capacitor plugins expose background data messages in different ways, and some plugins do not deliver them at all while the app is closed. Check the documentation for the plugin version that you use.
  3. Read your custom data from the dd_data value of the payload, as described in Handling custom data payloads.
🚧

The operating system does not guarantee silent push delivery

iOS treats silent pushes as low priority. The system may delay them, or it may not deliver them, depending on battery level, network conditions, and how often the user opens your app. Android applies similar restrictions when the device is in Doze mode.

Do not rely on a silent push for anything critical.

To find out how to send a silent push, see Send a silent push message.

Deep link support and link tracking

You can send a deep link with a push message. Your app is responsible for two things: reporting the tap, and opening the link.

🚧

Always report the tap

Call handlePushAndGetMessageDetails() whenever the user taps a push message. If you do not, the push reporting in Dotdigital Marketing does not show the tap. See Reporting the push tap for analytics.

Your app must support any deep link that you send in a push message. You can also send a link for another app, for example a link that opens a social media app.

A push message can carry a custom scheme such as myappscheme://product/123, or a web address such as https://example.com/product/123. To choose what a message sends, see Send a deep link or custom data with a push message.

Cordova based apps

To open a deep link in a Cordova based app, install the cordova-plugin-dotdigital-pushutils plugin:

cordova plugin add @comapi/cordova-plugin-dotdigital-pushutils

This plugin retrieves the link from the push notification, then asks the device to open it from native code. The link can be a deep link into your app, or a normal URL that points to a web page.

Implement the push.on event handler, which runs each time your app receives a push notification:

push.on('notification', (data) => {
    // Is there a deep link to process?
    if (cordova && cordova.plugins && cordova.plugins.dotdigitalPlugin) {
        // You do not need to call containsLink. It is safe to call handleLink for
        // all notifications. containsLink is available if you want to apply your
        // own logic first.

        if (cordova.plugins.dotdigitalPlugin.containsLink(data)) {
            cordova.plugins.dotdigitalPlugin.handleLink(data)
                .then(() => {
                    console.log("Deep link handled");
                })
                .catch((error) => {
                    console.log("Issue handling deep link", error);
                });
        }
    }
});

Register a URL scheme in your app

To open a deep link in your own app, you need a plugin that manages custom URL schemes. We recommend cordova-plugin-customurlscheme, because it is simple to set up. Other options include:

You may prefer a plugin that is designed for your framework.

Add the plugin, then specify the scheme that your app listens to. The scheme can be any short alphanumeric value that does not clash with an existing scheme:

cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=yourUrlScheme

This sets a deep link for the yourUrlScheme:// scheme.

Add this function to your app to interpret the deep links:

function handleOpenURL(url) {
    setTimeout(function() {
      console.log(`handleOpenUrl(${url})`);
      // Route to the correct screen in your app.
    }, 0);
}

To handle deep links correctly, you must also change the native project files.

iOS

Add the LSApplicationQueriesSchemes key to your info.plist file, so that your app can open deep links.

Then register your URL scheme in Xcode:

  1. In your app's project in Xcode, go to the Info tab. Under URL Types, select the + button.
  2. In the Identifier field, enter your app's bundle identifier, for example com.example.yourApp.
  3. In the URL Schemes field, enter your deep link scheme.
The Xcode Info tab with URL Types expanded, showing the Identifier and URL Schemes fields completed for a custom deep link scheme.

Android

Change the AndroidManifest.xml file for your app:

  1. Set android:launchMode to singleTask.
  2. Add an intent filter for your deep link scheme:
<intent-filter>
  <data android:scheme="yourUrlScheme"/>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
</intent-filter>

You can add the following configuration to the config.xml file of your Cordova project instead. Cordova then creates the intent filter for you:

<platform name="android">
   <allow-intent href="yourUrlScheme:*" />
</platform>

For platform guidance on deep links, see Android App Links and Apple's Defining a custom URL scheme for your app.

👍

Next steps

Make sure that your app passes an email address to the SDK for the app user. Dotdigital Marketing then creates a contact for that user. To do this, follow these instructions.

When your app can receive push messages, decide what each message does. See Send a deep link or custom data with a push message.


Did this page help you?