Skip to main content

Push Notifications

Push Notifications​

The Embrace SDK's PushNotificationCaptureService automatically records when your app receives a push notification or when the user taps one, so notification activity is visible alongside the rest of the session timeline.

How Push Notification Tracking Works​

The service swizzles the delegate setter on UNUserNotificationCenter and installs a proxy delegate. When a notification flows through the standard UNUserNotificationCenterDelegate callbacks:

  • userNotificationCenter(_:willPresent:withCompletionHandler:) — notification displayed while the app is in the foreground.
  • userNotificationCenter(_:didReceive:withCompletionHandler:) — user interacted with a delivered notification.

The proxy emits an OpenTelemetry span event named emb-push-notification on the currently active session part span, then forwards the call to your original delegate. Your delegate continues to run unchanged.

Where to find the data

Push activity is not a standalone root span. Look at the session part span's events timeline (or search span events by name emb-push-notification). It will not appear when filtering for root spans named emb.push.*.

What is not captured automatically

Only notifications routed through UNUserNotificationCenter delegate methods are captured. Silent / background notifications that your app handles only through UIApplicationDelegate.application(_:didReceiveRemoteNotification:fetchCompletionHandler:) are not intercepted. For those, capture the event manually (see Manual Capture below).

Configuration​

The service has a single option, captureData, which controls whether the user-facing payload fields (title, subtitle, body, category, badge) are recorded. It defaults to false.

let services = CaptureServicesOptionsBuilder()
.addDefaults()
.addPushNotificationCaptureService(withOptions: .init(captureData: false))
.build()

let options = EmbraceIO.Options.withAppId(
"APPID",
captureServices: services
//...other options
)
try EmbraceIO.start(options: options)

Captured Attributes​

Every emb-push-notification span event includes:

AttributeValueNotes
emb.typesys.push_notificationIdentifies the event type.
notification.typenotif or silentsilent when the payload contains aps.content-available == 1, otherwise notif.

When captureData: true, the service additionally reads the aps dictionary and adds the following attributes when the corresponding fields are present:

AttributeSource in the aps payload
notification.titlealert.title or alert.title-loc-key
notification.subtitlealert.subtitle or alert.subtitle-loc-key
notification.bodyalert.body or alert.body-loc-key
notification.categorycategory
notification.badgebadge (integer)

Manual Capture​

For notifications that don't pass through UNUserNotificationCenter (for example, background pushes handled in application(_:didReceiveRemoteNotification:fetchCompletionHandler:)), record them yourself with addPushNotificationEvent(userInfo:):

func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
EmbraceIO.shared.addPushNotificationEvent(userInfo: userInfo)
// ...your handling
completionHandler(.newData)
}

The userInfo dictionary must be APNS-shaped — a top-level aps key is required, otherwise the event is dropped. Minimal payload:

{
"aps": {
"alert": {
"title": "Notification Title",
"body": "Notification Body"
}
}
}

The parser additionally recognizes alert.subtitle, aps.category, aps.badge, and aps.content-available (1 marks the event as silent).

You can also record an event from a UNNotification directly inside a UNUserNotificationCenterDelegate callback with addPushNotificationEvent(notification:):

func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
EmbraceIO.shared.addPushNotificationEvent(notification: response.notification)
// ...your handling
completionHandler()
}

Both overloads accept an optional attributes: EmbraceAttributes dictionary that is merged into the event attributes — use this to attach campaign IDs, message IDs, or any other business context you need.

EmbraceIO.shared.addPushNotificationEvent(
userInfo: userInfo,
attributes: [
"campaign_id": campaignID,
"message_id": messageID
]
)

Manual events emitted this way honor the default captureData behavior (payload fields are included). If you want to suppress payload content, pass captureData: false.

Best Practices​

  • Leave captureData: false unless you need to read titles or bodies in the dashboard. Payload content frequently contains PII.
  • Make sure your app sets a UNUserNotificationCenter.delegate (the standard requirement to receive foreground notifications). The capture service piggybacks on the same delegate; if no delegate is ever assigned, the proxy still installs but only sees notifications that the system routes through it.
  • For background-only handlers, add a manual EmbraceIO.shared.addPushNotificationEvent(...) call as shown above — otherwise those deliveries will not appear in sessions.