> For a complete documentation index, fetch https://docs.voximplant.ai/llms.txt

# iOS: Push notifications

This article will help you to set up push notifications in **iOS applications**.

Voximplant push notifications for calls in iOS apps are based on a special type of Apple push notifications – [VoIP push notifications](https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/OptimizeVoIP.html#//apple_ref/doc/uid/TP40015243-CH30#//apple_ref/doc/uid/TP40008194-CH8-SW1).

The advantages of using VoIP push notifications for calls:

* The device is woken only when VoIP pushes occur, saving energy.
* Unlike standard push notifications, which the user should respond to before your app can perform an action, VoIP pushes go straight to your app for processing.
* VoIP pushes are considered high-priority notifications and are delivered without delay.
* VoIP pushes can include more data than what is provided with standard push notifications.
* Your app is automatically relaunched if it is not running when a VoIP push is received.
* Your app is given runtime to process a push, even if your app is operating in the background.

Limitations:

* VoIP push notifications are only available through the PushKit framework. If you have already integrated APNs push Notifications, you need to write additional code to support this type of push notifications.
* Since iOS 13, it is required to show an incoming call screen via CallKit, once a VoIP push is received. It is not possible to customize the incoming call screen. However, CallKit lets you integrate your calling services with other call-related apps on the system.

## Configure your iOS project

<Warning title="Before you start">
  Before you implement push notifications for audio and video calls, you need to implement CallKit integration to your project. Follow [this guide](https://voximplant.com/docs/guides/sdk/ios-callkit) to learn more.
</Warning>

1. Open the project in XCode.
2. Switch to the Project Navigator and click on the project.
3. Go to the **Signing & Capabilities** tab at the top panel. Check the [Adding capabilities to your App](https://developer.apple.com/documentation/xcode/adding_capabilities_to_your_app) Apple guide for more details.
4. Enable **Push Notifications** and the following **Background Modes**: "Audio, AirPlay, and Picture in Picture", "Voice over IP", "Background fetch", and "Remote notifications".

## Add certificates to the Voximplant Cloud

1. Generate a VoIP Push Services certificate in your Apple developer account.

![Choose VoIP certificate](https://files.buildwithfern.com/voximplant.docs.buildwithfern.com/62126827f1ed7e3d90b61fccda8f69f995e31b79e10e6bcd632047b981c90a4d/docs/assets/platform/sdks/guides-sdk-iospush-certificate.png)

2. Install the generated certificate to your Mac by double-clicking the downloaded file and importing it into the OSX Keychain of your Mac.
3. In the Keychain interface (choose the **Certificates** tab), right-click on the imported certificate and export it as a **.p12** file with a password.
4. Log in to the [Voximplant control panel](https://manage.voximplant.com/).
5. Go to your application, then switch to the **Push Certificates** tab.
6. Click on the **Add Certificate** in the upper right corner.
7. In the **Certificate for** list, select APPLE VOIP.
8. Click on **Choose File** and upload the **.p12** file.
9. Enter the password you have used while generating a file from your Keychain interface.
10. Select Production or Development mode for the certificate.
11. Click the **Add** button.

<Warning title="Production vs Development mode">
  Be careful while selecting the certificate mode as the push notification delivery depends on it.
</Warning>

Use Production mode only for the applications that are already released to the App Store or TestFlight.
Use Development mode only with application builds locally on the developer’s machines for debug purposes.

![Production vs Development mode](https://files.buildwithfern.com/voximplant.docs.buildwithfern.com/feabbea671863a79b630597f766d25407e1fd356b4ed0cf1697509ed7f1ae70c/docs/assets/platform/sdks/guides-sdk-iospush-calls.png)

The Voximplant Platform supports sending push notifications to several iOS applications with different package names within the same Voximplant application.

If it is required to add several APPLE certificates to the same Voximplant application, you have to set the application bundle name while adding the certificate.

<Warning title="Please note">
  If a bundle name is specified with a certificate, you should set the same bundle name when initializing the SDK.
</Warning>

<CodeBlocks>
  ```swift title="iOS (Swift)"
  let client = VIClient(delegateQueue: DispatchQueue.main, bundleId: "com.yourcompany.yourapp")
  ```

  ```objc title="Initialize SDK with a package name"
  VIClient *client = [[VIClient alloc] initWithDelegateQueue:dispatch_get_main_queue() bundleId:@"com.yourcompany.yourapp"];
  ```
</CodeBlocks>

<Info title="Tip">
  The Voximplant Control Panel allows you to add only one certificate of one type. If you are developing an application and need to test push notifications in both Debug and Release modes, add the same APPLE VOIP certificate with your application bundle + specific suffixes, for example, “.dev”, “.prod”.
</Info>

The Voximplant Cloud does not validate the bundle id, we use this parameter here only to identify the certificate needed to send a push notification. So, you don’t need to change the real bundle id, but only “mark” it for the Voximplant Cloud.

Do not forget to initialize the SDK correctly for Debug and Release configuration and set the suffix of the bundle id depending on the configuration.

## Modify your VoxEngine scenario

To let the Voximplant cloud send push notifications to applications, the JavaScript scenario that initiates a call should include a push notification helper:

```javascript title="Adding a push notification helper"
require(Modules.PushService);

```

## Provide a token to the Voximplant cloud

The Voximplant Cloud requires a device token to send a push notification for an incoming call. To get a VoIP push token, it is required to import the [PushKit framework](https://developer.apple.com/documentation/pushkit) and subscribe to push token updates. We recommend that you do it when starting the application. Although there are no requirements to place the implementation of PushKit integration to the AppDelegate, it is better to initiate PKPushRegistry as soon as possible.

To get the push token for the first time, it is required to subscribe to the [PKPushRegistryDelegate.pushRegistry(\_:didUpdate:for:)](https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate/1614470-pushregistry) method that is invoked when the system receives new credentials (including a push token) for the specified push type. If the push token is received earlier (for example, when the application was launched the previous time), this method is not invoked. However, you can get a locally cached push token from the [PKPushRegistry.pushToken(for:)](https://developer.apple.com/documentation/pushkit/pkpushregistry/1614472-pushtoken) method.

Once the token is obtained from the PushKit framework, it is needed to register it in the Voximplant Cloud via [VIClient.registerVoIPPushNotificationsToken(\_:completion:)](https://voximplant.com/docs/references/iossdk/client/viclient#registervoippushnotificationstokencompletion) API. The API provides the completion block that informs you when the token is ready to use or when an error occurs. If an error occurs, check [the error code](https://voximplant.com/docs/references/iossdk/client/vipushtokenerrorcode) for details.

[VIClient.registerVoIPPushNotificationsToken(\_:completion:)](https://voximplant.com/docs/references/iossdk/client/viclient#registervoippushnotificationstokencompletion) API may be called in any [VIClient](https://voximplant.com/docs/references/iossdk/client/viclient) state ([VIClientState](https://voximplant.com/docs/references/iossdk/client/viclientstate)), however, the token is registered only after the client logs in.

Register the token in these cases:

* Token has been updated by `<provider>`
* Another Voximplant user has logged in on the device

<CodeBlocks>
  ```swift title="iOS (Swift)"
  import PushKit

  let voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
  voipRegistry.delegate = self

  // check if pushToken is already available
  // if not, request it from PushKit (see pushRegistry(_:didUpdate:for:))
  if let token = voipRegistry.pushToken(for: .voIP) {
      // register push token
      // see pushRegistry(_:didUpdate:for:) implementation for an example
  } else {
      voipRegistry.desiredPushTypes = [.voIP]
  }

  func pushRegistry(_ registry: PKPushRegistry,
                    didUpdate pushCredentials: PKPushCredentials,
                    for type: PKPushType) {
      self.client.registerVoIPPushNotificationsToken(pushCredentials.token) { error in
          if let error = error {
              // Failed to register the push token, see error for details
          }
          // The push token is registered successfully
      }
  }
  ```

  ```objc title="Receiving the token"
  #import <PushKit/PushKit.h>

  PKPushRegistry *voipRegistry = [[PKPushRegistry alloc]
     initWithQueue:dispatch_get_main_queue()];
  voipRegistry.delegate = self;

  // check if pushToken is already available
  // if not, request it from PushKit (see - (void)pushRegistry:registry didUpdatePushCredentials:pushCredentials forType:type)
  NSData *token = [self.voipRegistry pushTokenForType:PKPushTypeVoIP];
  if (token) {
      // register push token
      // see - (void)pushRegistry:registry didUpdatePushCredentials:pushCredentials forType:type) implemenentation for an example
  } else {
      self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
  }

  - (void)pushRegistry:(PKPushRegistry *)registry
      didUpdatePushCredentials:(PKPushCredentials *)pushCredentials
                      forType:(PKPushType)type {
      [self.client registerVoIPPushNotificationsToken:pushCredentials.token
                                           completion:^(NSError * _Nullable error) {
          if (error) {
              // Failed to register the push token, see error for details
          }
          // The push token is registered successfully
      }];
  }
  ```
</CodeBlocks>

## Handle push notifications

VoIP push notifications are received by the [PKPushRegistry.(\_:didReceiveIncomingPushWith:for:completion:)](https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate/2875784-pushregistry) API of the PushKit framework.

Since iOS 13, Apple has the requirement to report any VoIP notifications to the CallKit framework by calling the [CXProvider.reportNewIncomingCall(with:update:completion:)](https://developer.apple.com/documentation/callkit/cxprovider/1930694-reportnewincomingcall) method. Reporting a call to CXProvider results in showing CallKit incoming call screen to the user. To show the call information such as the caller display name and the call type (audio or video call), parse the push payload to get all this information.

Instead of generating a random UUID for the CallKit, it is recommended that you use the NSUUID returned from the [VIClient.handlePushNotification(\_:)](https://voximplant.com/docs/references/iossdk/client/viclient#handlepushnotification) API. This UUID matches the [VICall.callKitUUID](https://voximplant.com/docs/references/iossdk/call/vicall#callkituuid) of the VICall instance received later and allows you to manage the call.

It is important to execute the completion handler of the [CXProvider.reportNewIncomingCall(with:update:completion:)](https://developer.apple.com/documentation/callkit/cxprovider/1930694-reportnewincomingcall) method only after the call processing is completed:

* The call has been connected, i.e. [VICallDelegate.call(\_:didConnectWithHeaders:)](https://voximplant.com/docs/references/iossdk/call/vicalldelegate#calldidconnectwithheaders) is invoked
* The call was rejected by a user via the CallKit UI
* Any internal error occurs, for example, failure to establish the connection to the Voximplant Cloud, login issues, etc

If you execute the completion handler of the PKPushRegistryDelegate API before the call processing is complete, the system may reduce the application resources which can affect the ability to establish a call successfully.

<Warning title="Pay attention">
  On iOS 13.0+, if you fail to report a call to CallKit, the system terminates your app. Repeatedly failing to report calls may cause the system to stop delivering VoIP push notifications to your app.
</Warning>

[VIClient.handlePushNotification(\_:)](https://voximplant.com/docs/references/iossdk/client/viclient#handlepushnotification) API can be called in any [VIClientState](https://voximplant.com/docs/references/iossdk/client/viclientstate), but to receive a VICall instance of an incoming call, it is required to connect to the Voximplant Cloud and log in. After you do that, the [VICallManagerDelegate.client(\_:didReceiveIncomingCall:withIncomingVideo:headers:)](https://voximplant.com/docs/references/iossdk/client/viclientcallmanagerdelegate#clientdidreceiveincomingcallwithincomingvideoheaders) method is invoked to provide access to the [VICall](https://voximplant.com/docs/references/iossdk/call/vicall) object.

<CodeBlocks>
  ```swift title="iOS (Swift)"
  private let callProvider: CXProvider = {
      let providerConfiguration = CXProviderConfiguration(localizedName: "AudioCallKit")
      providerConfiguration.supportsVideo = false
      providerConfiguration.maximumCallsPerCallGroup = 1
      providerConfiguration.supportedHandleTypes = [.generic]
      providerConfiguration.ringtoneSound = "ringtone.aiff"
      if let logo = UIImage(named: "AppLogo") {
          providerConfiguration.iconTemplateImageData = logo.pngData()
      }

      return CXProvider(configuration: providerConfiguration)
  }()

  @available(iOS 11.0, *)
  func pushRegistry(_ registry: PKPushRegistry,
                    didReceiveIncomingPushWith pushPayload: PKPushPayload,
                    for type: PKPushType,
                    completion pushProcessingCompletion: @escaping () -> Void) {
      if let callUUID = client.handlePushNotification(pushPayload) {
         let username = (pushPayload["voximplant"] as? [String:Any])["userid"] as? String
         let displayName = (pushPayload["voximplant"] as? [String:Any])["display_name"] as? String {

              let callinfo = CXCallUpdate()
              callinfo.remoteHandle = CXHandle(type: .generic, value: username)
              callinfo.supportsHolding = true
              callinfo.supportsGrouping = false
              callinfo.supportsUngrouping = false
              callinfo.supportsDTMF = true
              callinfo.hasVideo = false
              callinfo.localizedCallerName = displayName

              callProvider.reportNewIncomingCall(with: callUUID, update: callinfo)
              { (error: Error?) in
                  if let error = error {
                      // CallKit can reject new incoming call in the following cases (CXErrorCodeIncomingCallError):
                      // - "Do Not Disturb" mode is on
                      // - the caller is in the system black list
                      // - ...
                  }
                  // incoming call screen has been successfully shown to the user
              }
      }
  }
  ```

  ```objc title="Handling push notifications"
  - (CXProviderConfiguration *)providerConfiguration {
      CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"AudioCallKit"];
      providerConfiguration.supportsVideo = NO;
      providerConfiguration.maximumCallsPerCallGroup = 1;
      providerConfiguration.supportedHandleTypes = [NSSet setWithArray:@[[NSNumber numberWithInt:CXHandleTypeGeneric]]];
      providerConfiguration.ringtoneSound = @"ringtone.aiff";
      providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:@"AppLogo"]);
      return providerConfiguration;
  }

  - (void)pushRegistry:(PKPushRegistry *)registry
      didReceiveIncomingPushWithPayload:(PKPushPayload *)pushPayload
                                forType:(PKPushType)type
                  withCompletionHandler:(void (^)(void))completion {
      NSUUID *callUUID = [self.client handlePushNotification:pushPayload];
      NSString *displayName = [[pushPayload valueForKey:@"voximplant"] valueForKey:@"display_name"];
      NSString *username = [[pushPayload valueForKey:@"voximplant"] valueForKey:@"display_name"];

      CXProvider *callProvider = [[CXProvider alloc] initWithConfiguration:self.providerConfiguration];

      CXCallUpdate *callInfo = [[CXCallUpdate alloc] init];
      callInfo.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:fullUsername];
      callInfo.supportsHolding = YES;
      callInfo.supportsGrouping = NO;
      callInfo.supportsUngrouping = NO;
      callInfo.supportsDTMF = YES;
      callInfo.hasVideo = NO;
      callInfo.localizedCallerName = displayName;
      [callProvider reportNewIncomingCallWithUUID:newUUID update:callInfo completion:^(NSError * _Nullable error) {
          if (error) {
              // CallKit can reject new incoming call in the following cases (CXErrorCodeIncomingCallError):
              // - "Do Not Disturb" mode is on
              // - the caller is in the system black list
              // - ...
          }
          // incoming call screen has been successfully shown to the user
      }];
  }
  ```
</CodeBlocks>

Push notifications are sent automatically! Use the [callUser](https://voximplant.com/docs/references/voxengine/voxengine/calluser) method in the VoxEngine scenario and a push notification is automatically sent to the app. Thus, the app can wake up, connect to Voximplant, log in, and accept the call.

## Use tokens or keys for better security

Voximplant supports 3 ways of the authorization from SDK:

1. With a username and a password
2. With a username and a token
3. With a one-time login key

Saving the password is not secure, so we would recommend that you use the "token" or "key" approach.

The token is received from an event after a successful login and can be saved and used later instead of the password.

<Info title="One-time key">
  The "login key" approach allows you to completely avoid the use of a password on the client side by offloading it to your backend.
</Info>