iOS Push Notifications: Part 3 — Push payload

Dmitrijs Beloborodovs
Citadele Bank Developers
7 min readMar 15, 2024

In this article we review what actually push notification is as a container, what can it deliver to device.

Photo by Anja Bauermann on Unsplash

Push can be presented as a JSON document. A very simple push with only title and body texts is as simple as this

{
"aps": {
"alert": "Hello there!"
}
}

This is 42 bytes in formatted presentation and only 32bytes.

{"aps":{"alert":"Hello there!"}}

Push notification payload can be up to 4 KB. How much is 4 KB of text? This much:

Here is more complicated example

{
"aps": {
"alert": {
"title": "Lorem ipsum title",
"subtitle": "Lorem ipsum subtitle",
"body": "Lorem ipsum body"
},
"badge": 7,
"sound": "coin.aiff",
"relevance-score": 0.3,
"interruption-level": "active",
"category": "PAY_IN"
},
"my_id": "001"
}

With touch and hold or “force touch” the notification

  • alert contains title (1 line), subtitle (1 line) and body (2–4 lines) texts, which are optional, alternative you may provide title-loc-key, subtitle-loc-key and loc-key to provide localisation keys
  • There is also launch-image (not used here) for providing name for launch image if you wake up app opening notification. I didn’t get it working, seems no one did. If you know how it’s working, ping me in comments.
  • badge is for setting custom badge value or removing if value is 0
  • sound can specify a sound file for push or “default” for a system default sound. Custom one should not be more than 30 sec, otherwise system default is played.
  • relevance-score is a value (between 0 and 1) helping system to sort your notification and highest score is featured in notification summary.
  • interruption-level may be passive (notification added to notifications list, no sound, no lightning up screen, active (presents immediately, light up screen, can play sound), timeSensitive (see below) or critical (see below).
  • my_id is a custom field. You may want add much as you need until exceed payload size. Custom fields must be on aps level.
  • category informs which of the predefined categories this notification belongs. To define a category and associated item use:
        // Define the custom actions
let acceptAction = UNNotificationAction(
identifier: "ACCEPT_PAYMENT",
title: "Accept",
options: [.authenticationRequired]
)
let declineAction = UNNotificationAction(
identifier: "DECLINE_PAYMENT",
title: "Decline",
options: [.destructive]
)
// Define the notification type
let incomingPaymentCategory = UNNotificationCategory(
identifier: "PAY_IN",
actions: [acceptAction, declineAction],
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: "%u incoming payments",
options: .customDismissAction
)
// Register the notification type
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.setNotificationCategories([incomingPaymentCategory])

First, define actions providing identifier and type. For an accept action a device must be unlocked. Decline action marked as destructive. Second, define a notification type providing it’s identifier and associated actions. Third, register the notification type in system.

When device is locked, hiddenPreviewsBodyPlaceholder (see listing above) text shown instead of notification texts.

Once user press any of the buttons, perform action based on action identifier. If app is not running, it won’t be launched, only receive delegate call:

    func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse
) async {
// Perform the task associated with the action
switch response.actionIdentifier {
case "ACCEPT_PAYMENT":
// proceed with payment accept
break
case "DECLINE_PAYMENT":
// proceed with payment decline
break
default:
break
}
}

There are more attributes you can set. Some of them can only be used with pushes for Live Activity updates. See References below for links to Apple documentation.

Time Sensitive Notifications

This type of notifications (when defined in JSON payload as “interruption-level”: “active”) presented immediately, lights up the screen, can play a sound, and breaks through system notification controls. They break through the Focus and Do Not Disturb. But you need some extra setup. App ID configuration must include capability “Time Sensitive Notification”

Add one in Xcode: App target → Signing & Capabilities → Add capability

User may limit it app’s setting in Push Notifications section in Settings app.

Critical alerts

There is one more very special type of alert: critical. Critical alert (when defined in JSON payload as “interruption-level”: “critical”) will play sound even when device locked, muted or has Do Not Disturb enabled. For app to be able to accept this type, you must enabled special entitlement. Apply for request here:

Not all organisation may receive it. Since I don’t have this entitlement I can’t really tell you how to set up, but feel free to refer to numerous tutorials online, like this one. One thing worth mention, if you are allowed to send critical alerts (allowed by special entitlements issued by Apple), you may configure sound parameter as dictionary with critical (set 1 for “ON”), name for audio file name and volume (between 0 and 1).

"sound": {
"critical": 1,
"name": "coin.aiff",
"volume": 1
}

Push notification headers

There are some extra headers you may use when sending push to APNs. Those affect how notification delivered and processed by system.

  • apns-topic is optional if you use certificate based authentication with APNs. If authentication is token based, it must contain app bundle ID. For some notification types this field is required.
  • apns-priority may be 1 — low (attempt to deliver notification prioritising power considerations over all other factors, no device awaking), 5 — medium (power consideration) or 10 — high (deliver immediately).
  • apns-push-type must accurately reflect the contents of your notification’s payload. If there’s a mismatch, or if the header is missing on required systems, APNs may return an error, delay the delivery of the notification, or drop it altogether.
  1. alert is a simple push with banner, badge and sound.
  2. background type is about doing something in app without involving user interaction (will cover more in next articles).
  3. location is a special type (since iOS 15) which requires to implement app extension, app must ask user for sharing location always. After that you may write custom logic with location.
  4. voip deliver information about incoming Voice-over-IP call.
  5. complication contains updates for watchOS apps’ complications.
  6. fileprovider type for updating your File Providers extension content.
  7. mdm tell your managed device to contact MDM server.
  8. liveactivity updates your Live Activity session.
  9. pushtotalk (since iOS 16) updates your Push to Talk services.
  • apns-id is optional but may contain UUID which will be included in APNs response in case of error reported back.
  • apns-expiration is optional too. May contain date/time in UNIX epoch expressed in seconds (UTC). APNs will try to deliver notification if not succeeded immediately until provided date. If value is 0, APNs will try deliver only once.

apns-collapse-id is a max 64 bytes identifier which allow to merge (or substitute multiple notifications in one.

Xcode project source code available here.

References

--

--