Implementing Notification Service Extension for iOS
Notification Service Extension — a separate target in Xcode that intercepts incoming push notification before display. In this window (no more than 30 seconds), you can change the title, body, add media attachment, or decrypt payload. If Extension doesn't complete in time or crashes — the original notification is shown.
Practical Use Cases
Media attachments. Add an image to a push notification. Server sends URL in payload (mutable-content: 1 is required). Extension downloads the image through URLSession, saves to temporary directory, creates UNNotificationAttachment and passes it through contentHandler. Without Extension, attachments in push don't work.
End-to-end encryption. Payload arrives encrypted, Extension decrypts with a key from Keychain and substitutes readable text. Without this, push notifications in E2E messengers would show "New message" without content.
Delivery analytics. In Extension, make a fire-and-forget request to backend when notification is received — record delivered event. UIApplicationDelegate.userNotificationCenter(_:didReceive:) only fires on tap, Extension — on delivery.
Technical Details
Extension lives in a separate process, doesn't have direct access to main app data. For data sharing (for example, encryption keys), use App Groups: UserDefaults(suiteName: "group.com.example.app") and FileManager with group container.
Keychain Sharing: kSecAttrAccessGroup with group identifier allows Extension to read secrets recorded by the main app.
30-second timeout is strict. If downloading an image, set URLSession timeout less than 30 seconds with fallback: if didn't complete in time — show notification without attachment, don't fail Extension. URLSessionConfiguration.default.timeoutIntervalForRequest = 20.
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
// download attachment, on error — contentHandler(bestAttemptContent!)
}
override func serviceExtensionTimeWillExpire() {
// called just before timeout — show what we have
if let contentHandler, let content = bestAttemptContent {
contentHandler(content)
}
}
What's Included in the Work
- Creating and configuring Notification Service Extension target in Xcode
- Implementing logic: attachment, decryption, or analytics
- Setting up App Groups for data sharing with main app
- Testing on real device (Extension doesn't work in simulator for APNs push)
- Entitlements and provisioning profiles for Extension target
Timeframe
Implementing one scenario (for example, attachment): 1 day. With encryption, App Groups, and multiple processing logics: 2–3 days. Cost is calculated individually.







