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 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 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 = CaptureServiceBuilder()
.add(.pushNotification(options: .init(captureData: false)))
.addDefaults()
.build()

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

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 using the same event type:

func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
if let event = try? PushNotificationEvent.push(userInfo: userInfo) {
Embrace.client?.add(event: event)
}
// ...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 build an event from a UNNotification directly inside a UNUserNotificationCenterDelegate callback:

func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let notification = response.notification
if let event = try? PushNotificationEvent.push(notification: notification) {
Embrace.client?.add(event: event)
}
// ...your handling
completionHandler()
}

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

let event = try PushNotificationEvent.push(
userInfo: userInfo,
properties: [
"campaign_id": campaignID,
"message_id": messageID
]
)
Embrace.client?.add(event: event)

Manual events emitted this way honor the SDK's default captureData behavior (payload fields are included). If you want to suppress payload content, construct the event directly with captureData: false.

Best Practices

  • Leave captureData: false unless you need to read titles or bodies in the dashboard. Payload content frequently contains PII.
  • If you need campaign or message identifiers, add them server-side as top-level keys in the userInfo payload and capture them manually with the properties: parameter — there is no built-in payload filter.
  • 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 Embrace.client?.add(event:) call as shown above — otherwise those deliveries will not appear in sessions.