GitHub project

Silent push

New in v5.2.0

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, iOS wakes your app in the background and calls your app delegate. The SDK reads the push, reports Delivered to Customer.io, and gives your code the key/value data set on the message. A silent push never shows anything, so the SDK never reports Opened.

Silent push needs version 5.2.0 or later of the iOS SDK. If you install the SDK with CocoaPods, use version 5.2.1 or later, because CocoaPods doesn’t have version 5.2.0 of the CustomerIO pod.

Customer.io sends silent pushes to every iOS device a message targets, and doesn’t check which SDK version the device uses. On a device with an older SDK version, nothing reads the data and nothing reports Delivered.

Silent pushes don’t need the person’s permission to show notifications. Your app still needs to register for remote notifications, which the SDK does for you when autoFetchDeviceToken is on.

Set up silent push

  1. In Xcode, select your app target, go to Signing & Capabilities, add the Background Modes capability, and check Remote notifications. This adds remote-notification to UIBackgroundModes in your Info.plist. Without it, iOS doesn’t wake your app for a silent push.

  2. Initialize MessagingPushAPN or MessagingPushFCM in application(_:didFinishLaunchingWithOptions:) on every launch, as described in set up push notifications. iOS can launch your app in the background just to deliver a silent push, and that launch needs the SDK too.

  3. To read the data, call MessagingPush.shared.handleBackgroundPush(userInfo:) from application(_:didReceiveRemoteNotification:fetchCompletionHandler:). If you use CioAppDelegateWrapper, add this method to the AppDelegate class that the wrapper wraps. The code is the same for APNs and FCM.

    SWIFT
    import CioMessagingPush
    import UIKit
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
        func application(
            _ application: UIApplication,
            didReceiveRemoteNotification userInfo: [AnyHashable: Any],
            fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
        ) {
            switch MessagingPush.shared.handleBackgroundPush(userInfo: userInfo) {
            case .received(let push):
                // push.data holds the keys and values set on the message.
                // Key your work on push.deliveryID: the same push can arrive more than once.
                refreshAccount(push.data, deliveryID: push.deliveryID) {
                    completionHandler(.newData)
                }
            case .unsupported:
                // A Customer.io silent push the SDK can't read, like one with a value that isn't text.
                completionHandler(.noData)
            case .notBackground:
                // Not a Customer.io silent push. Pass it to your other handlers, then call completionHandler.
                completionHandler(.noData)
            }
        }
    }

Your app owns the completion handler. Call it once, as soon as your work ends.

How the SDK reports Delivered

When automatic push tracking (autoTrackPushEvents) is on, which is the default, the SDK reports Delivered for each silent push:

  • With CioAppDelegateWrapper, the SDK reports Delivered before your handler runs. It does this even if your app doesn’t implement application(_:didReceiveRemoteNotification:fetchCompletionHandler:).
  • Without the wrapper, handleBackgroundPush(userInfo:) reports it.

The SDK reports each delivery once, even when both the wrapper and your call to handleBackgroundPush(userInfo:) see the same push. If you turn automatic push tracking off, push.deliveryToken is the device token you need to report the delivery yourself.

Firebase’s app delegate proxy

If you use FCM with CioAppDelegateWrapper and leave 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 reach CioAppDelegateWrapper, so your app gets no data and the SDK doesn’t report Delivered. New installs get a version that works. If you use CocoaPods, run pod update GoogleUtilities to update an existing app. You can also set FirebaseAppDelegateProxyEnabled to NO in your Info.plist.

What to plan for

Apple decides when to deliver a silent push, and doesn’t always deliver it:

  • Time: iOS gives your app about 30 seconds to handle a silent push. Apps that run long or use a lot of power get woken less often.
  • Repeats: The same push can arrive more than once. Use push.deliveryID to skip work you already did.
  • Force-quit apps: iOS doesn’t wake an app the person swiped away in the app switcher. Silent pushes reach the app again after the person opens it.
  • Offline devices: While a device is offline, Apple keeps only the newest push for your app, so an earlier silent push can be dropped. Treat each silent push as a signal to sync, and fetch anything you missed from your own backend.
  • Throttling: Apple treats silent pushes as low priority and can delay or drop them, like in Low Power Mode. Apple’s guidance is to send no more than two or three an hour.
Updated November 13, 2026