Rich notification images
Notification Service Extension, payload contract, and MotiSigRichPushHandler on iOS.
iOS does not download remote image URLs by itself. To show an image inside the notification banner, your app must:
- Send the push with
mutable-content: 1(the MotiSig backend sets this automatically when an image URL is present). - Embed a Notification Service Extension (NSE) that downloads the URL and attaches it as a
UNNotificationAttachmentbefore iOS renders the banner.
The MotiSig Swift package ships MotiSigRichPushHandler so the NSE is a one-line forwarder. The same product is linked by both your app target and the NSE target.
Expected APNs payload
{
"aps": {
"alert": { "title": "...", "body": "..." },
"mutable-content": 1,
"sound": "default"
},
"messageId": "f082aa55-6eed-407f-b819-36e858ed7d0a",
"_motisig": {
"imageUrl": "https://your-cdn.example.com/path/push.jpg"
}
}Image URLs must be HTTPS and reachable from the NSE process — no auth headers, no signed cookies that depend on the host app. If your CDN requires auth, sign the URL itself (query-string token).
Image URL resolution order
MotiSigRichPushHandler.extractImageURLString(from:) returns the first non-empty match from:
_motisig.imageUrl/_motisig.image_url/_motisig.image(MotiSig canonical)_richContent.image(Expo push relay)fcm_options.image(FCM relay)- Top-level
image/imageUrl/image_url(host-app convenience)
This order matches the Android and Expo helpers so you can author one payload that works across MotiSig client SDKs.
Add the NSE target (Xcode, one-time)
- File → New → Target… → Notification Service Extension. Name it whatever you want (for example,
MotiSigNSE). - Set the extension's bundle identifier to a child of the host app's bundle id (for example,
com.your.app.MotiSigNSE). iOS rejects embedded extensions whose bundle id is not a prefix of the host's. - With the extension target selected → General → Frameworks and Libraries → +, add the
MotiSigSwift package product. The host app and the NSE linkMotiSigindependently; both bindings are normal. - Replace the auto-generated
NotificationService.swiftwith:
import MotiSig
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
MotiSigRichPushHandler.handle(request: request, contentHandler: contentHandler)
}
}- Build and run the host scheme. Confirm the extension is embedded by checking that
YourApp.app/PlugIns/MotiSigNSE.appexexists in the build products.
A working reference target lives in examples/motisig-ios-example/MotiSigNSE — open motisig-ios-example.xcodeproj in that example folder for a complete buildable sample.
Foreground banners
When the app is in the foreground, iOS does not show a banner unless the UNUserNotificationCenterDelegate returns banner presentation options. The MotiSig SDK installs its own delegate proxy and returns [.banner, .list, .sound] by default, so foreground banners — including the image attached by the NSE — appear without extra setup.
If your app installs its own UNUserNotificationCenterDelegate, make sure your willPresent returns options that include .banner (or .alert on older iOS), otherwise the foreground banner is suppressed even though the NSE attached the image correctly.
Tap handling
The image attachment is a presentation detail; tap callbacks (MotiSigNotificationListener.motiSig(didReceiveNotification:inForeground:)) and click tracking are unchanged. The same userInfo payload that fed the NSE is delivered to listeners, so you can still read _motisig.imageUrl (or any custom field) from notification.userInfo to render an in-app detail view.
Troubleshooting
| Symptom | What to check |
|---|---|
| NSE never invoked | aps.mutable-content == 1 in the delivered payload. Without it, iOS never hands the request to the extension. |
| NSE runs, no image | URL present under one of the resolved keys above; URL is HTTPS; URL is publicly reachable from the device's network. |
| Foreground banner missing image | Your app overrides the UNUserNotificationCenterDelegate and returns no banner options from willPresent. |
| Embedded-binary id error | The NSE bundle id is not a prefix-child of the host (for example, host com.your.app requires NSE id like com.your.app.MotiSigNSE). |
| Silent download failure | App Transport Security blocks plain http:// and self-signed certs. Open Console.app → filter by the NSE bundle id to inspect logs from inside the extension. |
For a deeper troubleshooting checklist covering the rest of the SDK, see Troubleshooting.
Expo apps
Do not add the NSE target manually for a managed Expo workflow. Follow Rich notification images for the Expo SDK and the expo-motisig-sdk example.