Push Notification Integration in Telegram-iOS: APNs and VoIP Push Implementation
Telegram-iOS uses standard Apple Push Notification service (APNs) for encrypted messages and PushKit for VoIP calls, with registration handled in AppDelegate.swift, decryption performed in a Notification Service Extension, and VoIP payloads routed through pushRegistryImpl to CallKit.
Telegram-iOS implements a sophisticated dual-path push notification integration that leverages both standard APNs for secure messaging and PushKit for real-time voice and video calls. This architecture, found in the TelegramMessenger/Telegram-iOS repository, cleanly separates concerns between the main app delegate for token registration, a notification service extension for payload decryption, and CallKit integration for incoming call handling.
APNs Registration for Regular Push Notifications
During application launch in submodules/TelegramUI/Sources/AppDelegate.swift, the app initiates registration for standard remote notifications. Inside application(_:didFinishLaunchingWithOptions:), the code checks for iOS 12.0 availability and requests an APNs token:
if #available(iOS 12.0, *) {
UIApplication.shared.registerForRemoteNotifications()
}
Source: lines ≈ 1597‑1599 in AppDelegate.swift.
When the system delivers the token via the delegate callback, Telegram stores it in a promise called regularDeviceToken for later use:
self.regularDeviceToken.set(.single(token))
Source: lines ≈ 30‑31 in AppDelegate.swift.
This token is subsequently bundled into NetworkInitializationArguments as part of the appData payload sent to Telegram servers. The configuration uses buildConfig.bundleData(... tokenType: "apns" ...) to include the token in the registration request.
Source: lines ≈ 71‑73 in AppDelegate.swift.
VoIP Push Registration with PushKit
For incoming voice and video calls, Telegram integrates PushKit to receive high-priority VoIP pushes even when the app is terminated. The implementation creates a PKPushRegistry instance and configures it for VoIP notifications:
let pushRegistry = PKPushRegistry(queue: .main)
pushRegistry.desiredPushTypes = Set([.voIP])
pushRegistry.delegate = self
self.pushRegistry = pushRegistry
Source: lines ≈ 1053‑1058 in AppDelegate.swift.
When the system provides VoIP credentials, the delegate stores the token in voipTokenPromise:
public func pushRegistry(_ registry: PKPushRegistry,
didUpdate credentials: PKPushCredentials,
for type: PKPushType) {
if #available(iOS 9.0, *), case PKPushType.voIP = type {
self.voipTokenPromise.set(.single(credentials.token))
}
}
Source: lines ≈ 2112‑2118 in AppDelegate.swift.
Similar to the APNs token, the VoIP token is incorporated into NetworkInitializationArguments and transmitted to Telegram's backend as part of the device registration process.
Handling Incoming VoIP Pushes and CallKit Integration
The PKPushRegistryDelegate implementation funnels incoming pushes to a centralized helper method called pushRegistryImpl. When a VoIP push arrives, the delegate method invokes this implementation:
public func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: @escaping () -> Void) {
self.pushRegistryImpl(registry, didReceiveIncomingPushWith: payload,
for: type, completion: completion)
}
Source: lines ≈ 2122‑2126 in AppDelegate.swift.
The pushRegistryImpl method performs four critical operations:
- Payload extraction – Locates either a plain
accountIdfield or an encryptedpfield in the push dictionary. - Decryption – Uses the per-account master notification key (
notificationEncryptionKeyId) stored in the account manager state to decrypt the payload. - Call identification – Checks for
loc-keyvalues of"CONF_CALL_REQUEST"or"CONF_VIDEOCALL_REQUEST"to identify incoming calls. - CallKit reporting – For calls, it generates a stable UUID via
CallSessionManager.getStableIncomingUUIDand reports the call to the system usingCXProvider.reportNewIncomingVoIPPushPayload, which presents the native incoming call UI. For non-call notifications, it writes the payload to a temporary file and triggers the Notification Service Extension.
Source: lines ≈ 2134‑2150 in AppDelegate.swift.
Notification Service Extension for Message Decryption
Regular message notifications are processed by the Notification Service Extension located in Telegram/NotificationService/Sources/NotificationService.swift. This extension runs when an encrypted APNs push arrives and performs the following:
- Retrieves the encrypted payload from the
"p"field. - Loads the master notification key from the shared container using
existingMasterNotificationsKey(postbox:). - Decrypts the content using
decryptedNotificationPayload(key:data:). - Builds rich notification content including avatars, custom emojis, and message previews via
NotificationContentContext.
The decryption flow inside NotificationServiceHandler.init follows this pattern:
guard let payloadData = Data(base64Encoded: encryptedPayload) else { … }
let notificationsKey = existingMasterNotificationsKey(postbox: stateManager.postbox)
let decryptedPayload = decryptedNotificationPayload(key: notificationsKey, data: payloadData)
Source: lines ≈ 3085‑3110 in NotificationService.swift.
After building the UNNotificationContent object, the extension returns it through the completion handler, allowing iOS to display the decrypted message to the user.
Summary
- APNs registration occurs in
AppDelegate.swiftviaUIApplication.shared.registerForRemoteNotifications(), storing the token inregularDeviceTokenfor backend registration. - VoIP registration uses
PKPushRegistrywith.voIPtype, saving credentials tovoipTokenPromise. - Push processing routes VoIP payloads through
pushRegistryImpl, which decrypts them and either reports to CallKit for calls or forwards to the Notification Service Extension for messages. - Decryption happens in
NotificationService.swiftusing per-account master keys retrieved from the shared container, enabling rich notification content with media and avatars.
Frequently Asked Questions
How does Telegram-iOS decrypt push notification payloads?
The app encrypts notification payloads server-side using per-account master keys. When a push arrives, the Notification Service Extension in NotificationService.swift retrieves the master key from the shared container using existingMasterNotificationsKey(postbox:), then decrypts the payload via decryptedNotificationPayload(key:data:). This allows the app to display message content and sender avatars without waking the main app process.
What is the difference between APNs and VoIP push handling in Telegram-iOS?
APNs pushes are handled by the Notification Service Extension and are used for messages, media, and group notifications. VoIP pushes are handled directly by the main app via PKPushRegistryDelegate in AppDelegate.swift, enabling immediate CallKit integration for incoming voice and video calls. VoIP pushes bypass the extension and are processed in pushRegistryImpl, which can wake the app from a terminated state.
How does the app register push notification tokens with Telegram servers?
Both APNs and VoIP tokens are stored in promise objects (regularDeviceToken and voipTokenPromise) during delegate callbacks. These tokens are then incorporated into NetworkInitializationArguments as part of the appData payload using buildConfig.bundleData(... tokenType: "apns"), which is transmitted to Telegram's backend during the network initialization sequence.
Where does CallKit integration occur for incoming calls?
CallKit integration happens inside pushRegistryImpl in AppDelegate.swift (around lines 2134‑2150). When the decrypted payload contains loc-key values indicating a call request ("CONF_CALL_REQUEST" or "CONF_VIDEOCALL_REQUEST"), the method generates a stable UUID and invokes CXProvider.reportNewIncomingVoIPPushPayload to present the system incoming call UI, ensuring the user can answer calls from the lock screen.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →