Receiving Incoming Calls With PushKit

We already talked about CallKit and how to use the system UI to display the standard calling UI for incoming/outgoing calls. CallKit is great for displaying the UI, but what when your app is in the background. Push Notifications are not reliable by design, so we need something better. Enter PushKit… In this article we’ll talk about receiving incoming calls with PushKit.

PushKit

Push notifications are not reliable. We don’t experience that because almost all of them get delivered almost instantaneously, but Apple does say that we shouldn’t use push notifications for critical functionality. We could use the silent notifications to notify the app about an incoming call, but why use push if we can use something more reliable 🙂

PushKit notification is more reliable than your normal push notification, but it’s very limited. PushKit only supports three notification types: VoIP, complications and file provider. So, out of those three almost everyone will be using the VoIP notification type 🙂 Let’s be honest, who remembers complications any more 🙂

The principles of PushKit notifications are very similar to your standard push notifications. If you understand one, you’ll understand the other. Let’s go over setting it up. We’ll use the ATCallKit demo app to integrate it. You’ll need an Apple Developer account if you want to follow along.

Certificates And Identifiers

Head over to your Apple Developer account and create a new App ID and make sure you select the ‘Push Notifications’ checkmark:

Next thing you’ll need is the VoIP certificate. Create a new certificate, when selecting a type, select the ‘VoIP Services Certificate’:

Just follow the instructions until you’re finished, and download your new ‘.cer’ file. Double click on the file to import it into the keychain. Now you can export the ‘.p12’ file, set a password when exporting:

One last thing we have to do is create a ‘.pem’ file. Open the terminal, navigate to the folder where you saved your ‘.p12’ file and type in:

After entering your password, you’ll have the ‘.pem’ file. We’ll need this to test our push notifications. Let’s set up the project next…

The Project

As you might have imagined, setting up the project for push notifications will be very complicated 🙂 Make sure you use the same bundle ID and enable the push notifications in the capabilities tab of the target:

Finally, time for some code 🙂

The Code

The only thing you’ll need is the push registry and the delegate callbacks. We’re only interested in VoIP notifications so we’ll set those as the desired push type. Make sure you set your delegate before registering for the types:

Make sure you conform to the delegate and implement the ‘didUpdate’ and ‘didReceiveIncomingPush’ functions. In one of the callbacks we’ll get the token:

The last callback that we want is the one that gets called when the push notification is delivered to the device:

Obviously, the payload will depend on your app. Here we’re just parsing a simple string that we’ll display on the UI.

Test It

Build and run. You’ll need to run it on a real device. When your app runs, make sure the token is printed in the console. Then type this in the terminal:

You can get the token from the console and make sure you replace the password placeholder with your own password. If everything went well you should see the incoming call UI:

Try killing the app, locking the phone and sending the push again. It should work:

That was pretty simple, wasn’t it 🙂

Conclusion

PushKit notifications have a very specific purpose. If your app falls into one of the categories covered by PushKit notifications then you can implement them pretty easily. If you want to learn more about PushKit you can read about it in the docs. You can also check out the example app we used here in the GitLab repo.

I hope you learned something new today and as usual…

Have a nice day 🙂
~D;

More resources

7 thoughts on “Receiving Incoming Calls With PushKit

  1. Madhuri Satpute

    Hi, is this functionality also works when app is killed. I am more interest for the same functionality when app is in kill state. I have tried to fire the same command to send notification from Terminal after the app is no more in Active or Background state, but it does not work.
    Can you please guid me?
    Thanks.

    Reply
  2. Jai

    The demo works fine when the app is running in the foreground. I get no response to sending the push however if the app is backgrounded or killed. I’m running on an iOS 13 device.

    Any help would be appreciated

    Reply
  3. Madhuri

    Hi Jai,
    I was facing the same issue.
    I managed to fix this by moving reportNewIncomingCall() in incomingCall() of ATCallManager class outside the asyncAfter block.
    I recommend you should also try this.
    Let me know if I can help you more.

    Reply
  4. Jai

    Hi Madhuri,

    Many thanks!

    My function now looks like this and it worked fine.

    [Posting it here in case others can make use of it!]

    public func incommingCall(from: String, delay: TimeInterval) {
    let update = CXCallUpdate()
    update.remoteHandle = CXHandle(type: .emailAddress, value: from)

    let bgTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay) {
    UIApplication.shared.endBackgroundTask(bgTaskID)
    }

    self.provider?.reportNewIncomingCall(with: UUID(), update: update, completion: { (_) in })

    }

    Reply
  5. JB

    Hello,

    Firstly, I would like to comment that its a great article

    I followed the steps but I am not receiving the push on device using the script. Is there tested way by which I can send sample VOIP pushes.

    (base) 3indbhnadar:MMS username$ curl -v -d ‘{“callerID”:”remote@example.com”}’ –http2 –cert voipCert.pem:xxx
    https://api.development.push.apple.com/3/device/891d52bf047f5372a4179d82b9426fc8c4b91366c47071b7de38f93fd08e7abd
    * Trying 17.188.166.27:443…
    * TCP_NODELAY set
    * Connected to api.development.push.apple.com (17.188.166.27) port 443 (#0)
    * ALPN, offering http/1.1
    * successfully set certificate verify locations:
    * CAfile: /Users/username/opt/anaconda3/ssl/cacert.pem
    CApath: none
    * TLSv1.3 (OUT), TLS handshake, Client hello (1):
    * TLSv1.3 (IN), TLS handshake, Server hello (2):
    * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
    * TLSv1.3 (IN), TLS handshake, Request CERT (13):
    * TLSv1.3 (IN), TLS handshake, Certificate (11):
    * TLSv1.3 (IN), TLS handshake, CERT verify (15):
    * TLSv1.3 (IN), TLS handshake, Finished (20):
    * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
    * TLSv1.3 (OUT), TLS handshake, Certificate (11):
    * TLSv1.3 (OUT), TLS handshake, CERT verify (15):
    * TLSv1.3 (OUT), TLS handshake, Finished (20):
    * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
    * ALPN, server did not agree to a protocol
    * Server certificate:
    * subject: CN=api.development.push.apple.com; OU=management:idms.group.533599; O=Apple Inc.; ST=California; C=US
    * start date: Apr 17 17:37:51 2019 GMT
    * expire date: May 16 17:37:51 2021 GMT
    * subjectAltName: host “api.development.push.apple.com” matched cert’s “api.development.push.apple.com”
    * issuer: CN=Apple IST CA 2 – G1; OU=Certification Authority; O=Apple Inc.; C=US
    * SSL certificate verify ok.
    > POST /3/device/891d52bf047f5372a4179d82b9426fc8c4b91366c47071b7de38f93fd08e7abd HTTP/1.1
    > Host: api.development.push.apple.com
    > User-Agent: curl/7.68.0
    > Accept: */*
    > Content-Length: 33
    > Content-Type: application/x-www-form-urlencoded
    >
    * upload completely sent off: 33 out of 33 bytes
    * Received HTTP/0.9 when not allowed

    * Closing connection 0
    * TLSv1.3 (OUT), TLS alert, close notify (256):
    curl: (1) Received HTTP/0.9 when not allowed

    Reply
  6. Cemil Canlı

    Can i use for incoming call silents pushes. Foreaxample: content avaible 1 and wake up then connect the char server and report new incoming call? Like this scenerio?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.