Silent push
A silent push wakes your app in the background and hands it the data set on the message, without showing anything to the person. Use this page to set your app up to receive silent pushes.
How it works
When you send a silent push, the device wakes your app in the background. The SDK reports Delivered to Customer.io, and your JavaScript gets the key/value data set on the message. A silent push never shows anything, so the SDK never reports Opened.
Silent push needs customerio-reactnative 7.3.0 or later. Customer.io sends silent pushes to every iOS and Android device a message targets, and doesn’t check which SDK version the device uses. An app that doesn’t handle silent pushes shows nothing.
Handle silent pushes in one place, depending on what receives push in your app:
| Your app | Handle silent pushes with |
|---|---|
| The Customer.io SDK handles push, through APNs or FCM | setBackgroundPushHandler |
@react-native-firebase/messaging receives your messages | React Native Firebase’s handlers |
If your app handles silent pushes in React Native Firebase and also sets setBackgroundPushHandler, each push can reach your code twice.
What a silent push contains
A silent push carries your data as text keys and values, plus two keys that Customer.io adds:
CIO-Background-Delivery-ID: identifies the delivery.CIO-Background-Delivery-Token: the device token the push was sent to.
setBackgroundPushHandler removes those two keys from the data and gives you their values as deliveryId and deliveryToken. React Native Firebase passes them through in remoteMessage.data.
With the Customer.io SDK
Set the handler in your entry file (index.js), before AppRegistry.registerComponent. On Android, a silent push that starts your app in the background runs index.js without mounting your app, so a handler you set inside a component would miss it.
// index.js
import { AppRegistry } from 'react-native';
import { CustomerIO } from 'customerio-reactnative';
import App from './src/App';
CustomerIO.pushMessaging.setBackgroundPushHandler(async ({ deliveryId, data }) => {
// data holds the keys and values set on the message.
// Key your work on deliveryId: the same push can arrive more than once.
await refreshAccount(data, { idempotencyKey: deliveryId });
});
AppRegistry.registerComponent('MyApp', () => App);- If your handler throws or rejects, the SDK logs the error and counts your handler as finished.
- Setting a new handler replaces the previous one. Call
remove()on the subscription thatsetBackgroundPushHandlerreturns to stop receiving silent pushes. - Your handler can run before any component mounts, so
CustomerIO.initializemight not have run yet. To call the SDK from your handler, initialize the SDK inindex.jstoo. - You only need
deliveryTokento report the delivery yourself withCustomerIO.trackMetricwhen you turn off automatic push tracking.
Android
You don’t need to do anything else on Android. The SDK runs your handler in a Headless JS task from a WorkManager job, either in your running app or in a React Native instance that it starts in the background. Your Application must provide a ReactHost, as the React Native template does.
A push that starts your app when it isn’t running reaches your handler once your JavaScript has set the handler at least once. If a later version of your app stops setting the handler, the first push that finds no handler within 25 seconds turns this off. Calling remove() turns it off right away.
iOS
- Add the Remote notifications background mode. In Xcode, go to Signing & Capabilities > Background Modes and check Remote notifications, or add
remote-notificationtoUIBackgroundModesin yourInfo.plist. - If you wrapped your
AppDelegatewithCioAppDelegateWrapper, as in push setup, you don’t need to do anything else. Once your JavaScript sets the handler, in this launch or an earlier one, the wrapper hands each Customer.io silent push to it and tells iOS when your handler finishes. Until then, and for every other notification, your app delegate gets the push as before. If iOS launched your app for the push, the push waits until your JavaScript sets the handler. - If you use FCM with
CioAppDelegateWrapperand Firebase’s app delegate proxy on, which is Firebase’s default, your app needs GoogleUtilities 8.1.5 or later. With earlier versions, silent pushes never reachCioAppDelegateWrapper. If Firebase comes from CocoaPods, runpod update GoogleUtilities. If it comes from Swift Package Manager, update your Firebase package. You can also setFirebaseAppDelegateProxyEnabledtoNOin yourInfo.plist.
As on Android, if a later version of your app stops setting the handler, the first push that finds no handler turns this off, and remove() turns it off right away.
If your app delegate isn’t wrapped with CioAppDelegateWrapper, forward background notifications to the SDK yourself:
import customerio_reactnative
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
if NativeMessagingPush.handleBackgroundPush(userInfo: userInfo, completionHandler: completionHandler) {
return // A Customer.io silent push. The SDK reports Delivered and calls completionHandler.
}
completionHandler(.noData) // Or pass the notification to your other handlers.
}With React Native Firebase
If @react-native-firebase/messaging receives your messages, handle Customer.io silent pushes in React Native Firebase’s handlers on both platforms, and don’t set setBackgroundPushHandler. messaging().onMessage receives a silent push while your app is in the foreground, and messaging().setBackgroundMessageHandler receives it while your app is in the background or not running.
Keep forwarding messages to Customer.io as described in multiple push providers, and don’t show a notification for a silent push yourself:
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
const handled = await CustomerIO.pushMessaging.onBackgroundMessageReceived(remoteMessage);
if (remoteMessage.data?.['CIO-Background-Delivery-ID']) {
// A Customer.io silent push: read your keys from remoteMessage.data.
await refreshAccount(remoteMessage.data);
return;
}
if (!handled) {
// Not a Customer.io message: handle it as before.
}
});- Android:
onMessageReceivedandonBackgroundMessageReceivedshow nothing for a silent push, report Delivered, and resolvetrue. React Native Firebase starts a service to runsetBackgroundMessageHandler, and Android 8 and later refuse to start it for a normal-priority message once your app has been in the background for a few minutes. When that happens, neither your handler nor Customer.io sees the push. On this route, set the message’s Android priority to High. Firebase may still lower the priority of high-priority messages that keep showing no notification, and a lowered message can miss this route the same way. - iOS: Add the Remote notifications background mode, as described in iOS.
onMessageReceivedandonBackgroundMessageReceiveddo nothing on iOS and resolvetrue. The SDK reports Delivered as the push passes throughCioAppDelegateWrapper.
What to plan for
Apple and Google decide when to deliver a silent push, and don’t always deliver it:
- Repeats: The same push can arrive more than once. Use the delivery ID to skip work you already did.
- Time: Keep your work short. The SDK waits up to 25 seconds for your handler, including the time React Native takes to start.
- Delays: Android can hold normal-priority messages while the device is idle. iOS throttles background notifications, and keeps only the newest one for a device that’s offline.
- Force-quit apps: iOS doesn’t wake an app the person force-quit.