# Push notifications Use Apple **VoIP** push notifications together with the Sinch SDK and the system calling UI ([CallKit](https://developer.apple.com/documentation/callkit) or [LiveCommunicationKit](https://developer.apple.com/documentation/livecommunicationkit)) for reliable incoming-call delivery and a native user experience. To fully enable VoIP push notifications in your application, this document will guide you through these in more detail: - Configure your iOS app for VoIP push notifications and **CallKit** / **LiveCommunicationKit**. - Create and upload an *APNs Signing Key* for your Sinch Application in the Sinch Developer Portal. - Configure `SinchClient` so Sinch manages push notifications (client-side and server-side). - Integrate Sinch push APIs with **CallKit** / **LiveCommunicationKit**. - Ensure the [APNs environment](https://developer.apple.com/documentation/bundleresources/entitlements/aps-environment) matches the app entitlements and code signing. Please, check our reference application for full implementation and additional functionalities, which is available on [GitHub](https://github.com/sinch/rtc-reference-applications/tree/master/ios). Apple requires VoIP pushes that represent calls to be reported to the system calling UI (CallKit / LiveCommunicationKit) before your push delegate returns. Failing to do so may result in app termination or future VoIP pushes being throttled. ## Configure iOS app with Push Notifications capability Enable the **Push Notifications** capability and ensure your app entitlements include the correct [APS environment](https://developer.apple.com/documentation/bundleresources/entitlements/aps-environment). ## Acquiring a push device token `SinchManagedPush` is a component used to simplify acquiring a push device token and registering the token with a `SinchClient`. `SinchManagedPush` will make use of *PushKit* to acquire a push device token, and will automatically register the token with the `SinchClient` when a client is created (later in the application life cycle). `SinchManagedPush` should be created as early as possible in the application's life cycle. ```swift class AppDelegate: UIResponder, UIApplicationDelegate { // Create instance to enable push notification. private var sinchPush: SinchManagedPush? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { ... sinchPush = SinchRTC.managedPush(forAPSEnvironment: .development) sinchPush?.delegate = self sinchPush?.setDesiredPushType(SinchManagedPush.TypeVoIP) } } ``` APS environment must match The APNs environment you pass to `SinchRTC.managedPush(forAPSEnvironment:)` **must match** the app’s provisioning (Development vs. Production). Please see the section [APS Environments and Provisioning](#apple-push-service-environments-and-provisioning) for details. `SinchManagedPush` is lightweight and can live independently of a `SinchClient`. Create it once and keep it for the lifetime of the app. ## Enabling push notifications for `SinchClient` To make Sinch manage push notifications for you end-to-end, both client-side and server-side in terms of acting as an [APNs Provider](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns?language=objc), it's necessary to enable managed push notifications when configuring a `SinchClient`. ```swift do { sinchClient = try SinchRTC.client(withApplicationKey: "yourApplicationKey", environmentHost: "ocra.api.sinch.com", userId: "userId", cli: "cli") } catch let error as NSError { // Handle error. } sinchClient.enableManagedPushNotifications() ``` ## Configuring an APNs authentication signing key Provide Sinch with your **APNs Authentication Token Signing Key** so we can send VoIP pushes on your behalf. You create this signing key in your [Apple Developer Account](https://developer.apple.com/) and upload the key file to your [Sinch Developer Account](https://dashboard.sinch.com/voice/apps). 1. Create an APNs Key in your [Apple Developer Account](https://developer.apple.com/). See the Apple developer documentation on creating the key [here](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns). 2. Upload the key file (`.p8`) for your Sinch Application in your [Sinch Developer Account](https://dashboard.sinch.com/voice/apps). > 🔒 Your Signing Key is Stored Securely Sinch will store your signing key in encrypted form using `AES256-GCM` (AES in GCM mode, using 256-bit keys). We only support APNs Token-based authentication. We no longer support APNs certificate-based functionality. ## CallKit ### Apple requirements for VoIP push notifications for CallKit Apple introduced stricter requirements for using VoIP push notifications since iOS 13. iOS apps built using the iOS 13 SDK and make use of VoIP push notifications must report each received push notification as an incoming call to CallKit. Report notifications to the CallKit framework by calling the method `CXProvider.reportNewIncomingCall(with:update:completion:)`. Further, it must report this **within the same run-loop**, before the delegate method invocation scope `PKPushRegistryDelegate.pushRegistry(_:didReceiveIncomingPushWith:for:completion:)` ends. In terms of the Sinch SDK, this means your implementation must report to CallKit in your implementation of `SinchManagedPushDelegate.managedPush(_:didReceiveIncomingPushWithPayload:for:)`. ```swift func managedPush(_ managedPush: SinchRTC.SinchManagedPush, didReceiveIncomingPushWithPayload payload: [AnyHashable: Any], for type: String) { // Extract call information from the push payload. let notification = queryPushNotificationPayload(payload) guard notification.isCall, notification.isValid else { return } let callNotification = notification.callResult let uuid = // Get uuid from mapped callNotification.callId let update = CXCallUpdate() update.remoteHandle = CXHandle(type: .generic, value: callNotification.remoteUserId) self.provider.reportNewIncomingCall(with: uuid, update: update) { // Handle error and hangup call if needed. } } ``` If you do **not** relay the payload to a `SinchClient`, call `SinchManagedPush.didCompleteProcessingPushPayload(_:)` so that PushKit’s completion handler is invoked. Failing to report to CallKit If a VoIP push notification is not reported to CallKit then iOS will terminate the application. Repeatedly failing to report calls to CallKit may cause the system to stop delivering any more VoIP push notifications to your app. The exact limit before this behavior kicks in is subject to Apple iOS implementation details and outside the control of the Sinch SDK. Please also see [Apple's Developer documentation on this topic](https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate/2875784-pushregistry). ### Reporting outgoing calls to CallKit While reporting incoming calls to CallKit's mandatory in order to process incoming VoIP push notifications, the same limitation doesn't apply to outgoing calls. Nevertheless, reporting outgoing calls to CallKit's still required in scenarios when an outgoing call is established (callee answers the call) while the caller app is in background, or the caller device is in locked state. In such scenarios, as a privacy measure the OS will prevent the audio unit from being initialized for recording because the app is not in foreground, unless the outgoing call is reported to CallKit. For this reason the recommendation is that outgoing calls should be reported to *CallKit* as well. ```swift func call(userId: String, uuid: UUID, with completion: @escaping (Error?) -> Void) { let handle = CXHandle(type: .generic, value: userId) let startCallAction = CXStartCallAction(call: uuid, handle: handle) let startCallTransaction = CXTransaction(action: startCallAction) self.callController.request(startCallTransaction, completion: completion) } ``` Implement `CXProviderDelegate` to handle the call actions: ```swift func provider(_ provider: CXProvider, perform action: CXStartCallAction) { ... let recipientIdentifier = action.handle.value let callResult = callClient.callUser(withId: recipientIdentifier) switch callResult { case .success(let call): ... // Assigning the delegate of the newly created SinchCall // to track call establishment, progress and ending. call.delegate = self action.fulfill() case .failure(let error): action.fail() } } ``` ## LiveCommunicationKit Use LiveCommunicationKit (*iOS 17.4+*) where CallKit is unavailable or when you prefer the new system calling UI. You still use the Sinch SDK for the media/signaling; LiveCommunicationKit provides the system UI layer. ### Apple requirements for VoIP push notifications for LiveCommunicationKit Report each VoIP push that represents an incoming call to LiveCommunicationKit **before** your PushKit delegate returns, then relay the payload to `SinchClient`. ```swift func managedPush(_ managedPush: SinchRTC.SinchManagedPush, didReceiveIncomingPushWithPayload payload: [AnyHashable: Any], for type: String) { // Extract call information from the push payload. let notification = queryPushNotificationPayload(payload) guard notification.isCall, notification.isValid else { return } let callNotification = notification.callResult let uuid = // Get uuid from mapped callNotification.callId let localHandle = Handle(type: .generic, value: "localUserId") let remoteHandle = Handle(type: .generic, value: callNotification.remoteUserId) let update = Conversation.Update(localMember: localHandle, activeRemoteMembers: [remoteHandle], capabilities: nil) Task { do { try await self.conversationManager.reportNewIncomingConversation(uuid: uuid, update: update) } catch { // Handle error and hangup call if needed. } } } ``` ### Reporting outgoing conversations to LiveCommunicationKit Trigger the system UI using `StartConversationAction`, then start the Sinch call when the system asks you to perform the action. ```swift func call(userId: String, uuid: UUID, with completion: @escaping (Error?) -> Void) { let handle = Handle(type: .generic, value: userId) let startConversationAction = StartConversationAction(conversationUUID: uuid, handles: [handle], isVideo: false) Task { do { try await self.conversationManager.perform([startConversationAction]) } catch { // Handle error. } } } ``` Implement `ConversationManagerDelegate` to handle the call actions: ```swift func conversationManager(_ manager: ConversationManager, perform action: ConversationAction) { switch action { case let start as StartConversationAction: return self.perform(action: start) // Other actions processing can be added here. default: action.fulfill() } } private func perform(action: StartConversationAction) { ... guard let recipientIdentifier = action.handles.first?.value, !recipientIdentifier.isEmpty else { action.fail() callStartedCallback?(.failure(CallError.noRecepientIdentifier)) return } let callResult = callResult = callClient.callUser(withId: recipientIdentifier) switch callResult { case .success(let call): ... // Assigning the delegate of the newly created SinchCall. // To track call establishment, progress and ending. call.delegate = self action.fulfill() case .failure(let error): action.fail() } } ``` ## Extracting call information from a push payload At the time when your application receives a push notification you will need to extract some key information about the call based on only the push notification payload. You will need to do this to conform with Apple requirements on reporting a VoIP push notification as an incoming call to CallKit / LiveCommunicationKit, but you may also want to extract application-specific headers for the call. Use `SinchManagedPush.queryPushNotificationPayload(_:)` to parse push payloads and obtain call metadata (remote user ID, video offered, headers, etc). You can do this even before creating a `SinchClient`. ```swift let notification = queryPushNotificationPayload(payload) guard notification.isCall, notification.isValid else { return } let callInfo = notification.callResult ``` ## Unregister a push device token If the user of the application logs out or performs a similar action, the push notification device token can be unregistered using the method `SinchClient.unregisterPushNotificationDeviceToken()` to prevent further notifications to be sent to the particular device. ## Apple Push Service environments and provisioning When an iOS application is code signed, the embedded Provisioning Profile will have to match the Apple Push Notification Service Environment (also referred to as APS Environment) specified in the app [Entitlements](https://developer.apple.com/documentation/bundleresources/entitlements/aps-environment). This means how the app is code signed and what Provisioning Profile is used has an effect on what value should be passed to `SinchRTC.managedPush(forAPSEnvironment:)`. For example, if your application is signed with a *Development* provisioning profile it will be bound to the APS *Development* environment. If it's code signed with a *Distribution* provisioning profile it will be bound to the APS *Production* environment. Typically a *Debug* build will be code signed with a *Development* provisioning profile and thus `APSEnvironment` should be used. And typically a *Release* build will be code signed with a *Distribution* provisioning profile and thus `APSEnvironment` should be used. **You are responsible for selecting proper entitlements depending on your build type and signing profile.** ## iOS not delivering notifications Under certain circumstances, iOS won't deliver a notification to your application even if it was received at device/OS level. Note that this also applies to VoIP push notifications. Exact behavior and limits are subject to iOS internal details, but well-known scenarios where notifications won't be delivered are: - The end-user has actively terminated the application. iOS will only start delivering notifications to the application again after the user has actively started the application again. - Your app hasn't been reporting VoIP push notifications to CallKit / LiveCommunicationKit. Please see the separate sections above on how to report VoIP push notifications. ## Apple resources - [PushKit](https://developer.apple.com/documentation/pushkit) - [CallKit](https://developer.apple.com/documentation/callkit) - [LiveCommunicationKit](https://developer.apple.com/documentation/livecommunicationkit) - [Responding to VoIP Notifications from PushKit](https://developer.apple.com/documentation/pushkit/responding_to_voip_notifications_from_pushkit) ## `SinchManagedPush` and `SinchClient` interaction This section covers details on how `SinchManagedPush` and `SinchClient` interact together (automatically). `SinchManagedPush` will make use of `PKPushRegistry` to acquire a push device token. If any `SinchClient` instances exist, it will register the token via `SinchClient.registerPushNotificationDeviceToken:forPushType:apsEnvironment:)`, which will in turn register the token with the Sinch backend platform. If no instance of `SinchClient` exists when `SinchManagedPush` initially acquires the token, it will hold on to the token (in-process memory only) and register it with any `SINClient` that's created later during the whole application life cycle.