iOS 13 PushKit VoIP restrictions breaking SIP VoIP apps

Hi,


We are a fairly large company providing smartphone and desktop applications for VoIP/UC together with PBX systems, on-premise as well as clourd offerings. All our app are talking to the telephony servers over SIP.

Up unitl now, our flow on iOS was:

  1. Receive PushKit VoIP notification
  2. REGISTER towards telephony server
  3. Receive INVITE
  4. reportIncomingCall


With the changes enforced and outlined in https://developer.apple.com/videos/play/wwdc2019/707/, the whole SIP concept will break.

We are now forced to report an incoming call so early that the user might accept the call before the REGISTER has completed and an INVITE has been received which will lead to a pretty bad user experience.

Even worse, we will report an incoming call even if there is no actual call. This might happen as the call has already been canceled on the remote end and we won't get an INVITE. Or if the registration fails due to network issues that prevents us from reaching the telephony server.


How does Apple expect us to deal with these situations?

It's not possible to perform the whole REGISTER/INVITE stuff "in the same run loop as pushRegistry:didReceiveIncomingPushWithPayload:forType:[withCompletionHandler:] without delay."


I'm not sure what our support and sales guys will do when they receive negative customer feedback with the changed implementation. Maybe tell existing and new customers to use Android smartphones instead of iPhones...


There has to be at least some way of delaying the reportIncomingCall so that an application can check against the telephony server if there is a call and report if there is none to avoid punishment for not reporting the call.

Accepted Reply

A lot of different issues have been raised in this thread, so I've loosely summarized them to the following 4 questions issues:



1) Reporting a call immediately does not work with the SIP register-invite flow


On iOS 13, there are cases where you will need to initiate a CallKit call that you previously would have silently ignored. However, in practice, this should not be the common case, particularly for truly “non-existent" calls, since that would mean you had been notifying the user of calls that did not exist.


The more common cases here are calls that have either already ended (because the caller hung up) or can't be completed (because network conditions prevent the call from connecting). While calls will be created for both of these cases, there are few different techniques that you can use to mitigate any disruptions:


While you must report an incoming call immediately, you are still free to decide the call has failed and tell the system later, asynchronously. To do this, call reportCallWithUUID:endedAtDate:reason:. This will tear down the incoming call UI even if the user has not answered the call.


If the user is particularly fast at tapping the accept call button, or if the network conditions are poor or there is otherwise a lot of latency, then you should simply wait until the necessary handshakes are complete before calling fulfill on the CXAnswerCallAction. To the user, it simply appears that the call is taking some time to connect, which is a common experience even with standard phone calls.


Note that the system takes a few seconds for the incoming call UI to animate in, during which the app has the opportunity to complete this handshake, so this will only have a user-visible impact if it takes a significant time for the handshake to complete.


At any time, you can asynchronously update the UI with the reportCallWithUUID:updated: API. That means that if you cannot put the caller ID info in the push payload, you can simply choose to present dummy information (like "Connecting Call..." for the caller name) and update it asynchronously once they get the real information from your server.


2) Sending a push to cancel an incoming call


While your app currently has an active call (ringing or answered), your app is not required to create additional calls for VoIP pushes received during this call. This is intended to be used to support advanced functionality like dynamic call priority, but it could also be used to cancel an incoming call.


Having said that, this is not an approach I would recommend or rely upon. The reality is that as soon as your app receives it's PushKit notification it should be connecting to your server, so unless network conditions are very poor, you should be able to communicate to your client through that connection faster than PushKit. More to the point, if network conditions mean that you can't connect to your server, then trying to handle this with another push isn't a great idea either, since poor connectivity (and timing generally) opens the door to edge cases you'd want to avoid - for example, a client receiving ONLY the cancelation and not the original call notification.


As a side note here, keep in mind that as part of adapting to the new requirements you'll want to make sure the VoIP notification has a short or zero "apns-expiration" to prevent newly available devices from being notified of out-of-date calls. This will also minimize the cancelation issue, since a “newly available device" (for example, a phone that was just powered on) will ONLY receive notifications about calls that are occurring at that particular instant.


3) Block-lists/Do Not Disturb


CallKit respects the system Do Not Disturb setting, so most apps will not have to worry about system-level Do Not Disturb functionality. If your app has it's own blocking/do not disturb system built in, you can also maintain that list server-side and not send pushes to the blocked devices. If you absolutely need to do "local" call blocking, then you can report a call and then end it. The call will be briefly visible to the user, but you can also configure the source of the call to communicate what's going on ("Blocked Call...").


4) Using VoIP pushes to trigger syncs or other non-VoIP use cases


VoIP pushes were always intended to specifically support call notifications and nothing else. The good news here is that using Notification Service Extension is the best substitute for most of the functionality that you previously handled with PushKit:


https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension


A few examples:

  • For general messaging, you can connect to your server in the extension delegate, download any missed messages, and then update the notification content as appropriate.
  • The same approach can be used to tell the user about calls missed while the device was offline and/or pending voicemails.
  • For non-user facing functionality, like data synchronization or other app maintenance, I would recommend checking out the new BackgroundTasks framework:


https://developer.apple.com/documentation/backgroundtasks


Kevin Elliott

Developer Technical Support

Core OS/Hardware

Replies

We are in a similar situation. Like the post above, the user may answer the call before the app is ready to receive the call.


In addition, there may be cases where a VOIP notification is received and we do not want to trigger the incoming call UI. For example:


  • A local settings (i.e., call block or app-level do-not-disturb) indicates that the user does not want to receive the call.
  • Other situations where a VOIP notification might be sent to trigger a critical sync between devices.


Our concern is that these use cases could result in the notificaton not getting reported to CallKit and potentially trigger a violation on iOS 13. What is the recommendation for these use cases?

>> Even worse, we will report an incoming call even if there is no actual call.


I too am very interested in what Apple have to say about this.


Also what are your thoughts on blocking calls? Does your app currenlty permit users of the app to block certain numbers? I don't see how an app-side blocking mechanism could possibly work now because the call screen will always be displayed (previously the app could just ignore any voip pushes for blocked numbers, now this is no longer possible)

Hi langaCom


We too are in EXACTLY the same situation with iOS13.


I would echo your question ...


How does Apple expect us to deal with these situations?

It's not possible to perform the whole REGISTER/INVITE stuff "in the same run loop as pushRegistry:didReceiveIncomingPushWithPayload:forType:[withCompletionHandler:] without delay."


We have built a business which since the introduction of callkit and pushkit gives a great VoIP user experience. (Much better than Android) Now huge architectural changes (ie not using standard VoIP Register/ Invite) are required to be implemented somehow in a rush for iOS13 release.


I would urge Apple to please shelve or scrap this change. Genuine VoIP Apps will be hit badly

+1 with the exact same issue. We contacted Apple and got a generic and useless "check App Store Guidelines" answer, we cannot submit a TSI for beta software and there's no official answer here on the forums by Apple.


If Apple really wants to prevent the misuse of VoIP pushes, why not limit their access to a special entitlement, like they did on the Critical Alerts feature?


I would very much like to know how are the "big players" like WhatsApp, Messenger, etc going to deal with this change...

We are in a similar boat. We have an Enterprise calling app, and we need to be able to send a "Cancel" push to know that the call is no longer active - so we can cancel/end calls that have been ended by the calling user.


This is for the workflow of:


No Connectivity

* User is unavaiable for a few minutes (in an area with bad WiFi)

* During that time they get a call - so they queue up an 'Incoming Call' Push

* Call rings out (takes 30 seconds)

* Send a 'Cancel' Push

* User walks back into WiFi coverage - they receive the 'Incoming Call' & 'Cancel' Pushes - we don't want to ring at all


Canceled Call

* User gets the Incoming Call push - phone starts ringing

* Calling user cancels the call after a few seconds - accidentally called the wrong person

* Send a 'Cancel' push to end the call


Neither of these workflows would work anymore - which leads to a REALLY bad user experience.


There are other ways to ensure the PushKit capability is not being abused - this really makes it almost impossible to be a SIP based VOIP application, and have a good user experience.

Hello,


We are in the similar boat. We provide Voice SDK for VoIP calls. We expect two VoIP push notification : A new incoming Call push notificaiton, and Call cancellation push notification.


Is there a guideline on how to handle call cancellation scenario?


Thanks you!


Piyush

A lot of different issues have been raised in this thread, so I've loosely summarized them to the following 4 questions issues:



1) Reporting a call immediately does not work with the SIP register-invite flow


On iOS 13, there are cases where you will need to initiate a CallKit call that you previously would have silently ignored. However, in practice, this should not be the common case, particularly for truly “non-existent" calls, since that would mean you had been notifying the user of calls that did not exist.


The more common cases here are calls that have either already ended (because the caller hung up) or can't be completed (because network conditions prevent the call from connecting). While calls will be created for both of these cases, there are few different techniques that you can use to mitigate any disruptions:


While you must report an incoming call immediately, you are still free to decide the call has failed and tell the system later, asynchronously. To do this, call reportCallWithUUID:endedAtDate:reason:. This will tear down the incoming call UI even if the user has not answered the call.


If the user is particularly fast at tapping the accept call button, or if the network conditions are poor or there is otherwise a lot of latency, then you should simply wait until the necessary handshakes are complete before calling fulfill on the CXAnswerCallAction. To the user, it simply appears that the call is taking some time to connect, which is a common experience even with standard phone calls.


Note that the system takes a few seconds for the incoming call UI to animate in, during which the app has the opportunity to complete this handshake, so this will only have a user-visible impact if it takes a significant time for the handshake to complete.


At any time, you can asynchronously update the UI with the reportCallWithUUID:updated: API. That means that if you cannot put the caller ID info in the push payload, you can simply choose to present dummy information (like "Connecting Call..." for the caller name) and update it asynchronously once they get the real information from your server.


2) Sending a push to cancel an incoming call


While your app currently has an active call (ringing or answered), your app is not required to create additional calls for VoIP pushes received during this call. This is intended to be used to support advanced functionality like dynamic call priority, but it could also be used to cancel an incoming call.


Having said that, this is not an approach I would recommend or rely upon. The reality is that as soon as your app receives it's PushKit notification it should be connecting to your server, so unless network conditions are very poor, you should be able to communicate to your client through that connection faster than PushKit. More to the point, if network conditions mean that you can't connect to your server, then trying to handle this with another push isn't a great idea either, since poor connectivity (and timing generally) opens the door to edge cases you'd want to avoid - for example, a client receiving ONLY the cancelation and not the original call notification.


As a side note here, keep in mind that as part of adapting to the new requirements you'll want to make sure the VoIP notification has a short or zero "apns-expiration" to prevent newly available devices from being notified of out-of-date calls. This will also minimize the cancelation issue, since a “newly available device" (for example, a phone that was just powered on) will ONLY receive notifications about calls that are occurring at that particular instant.


3) Block-lists/Do Not Disturb


CallKit respects the system Do Not Disturb setting, so most apps will not have to worry about system-level Do Not Disturb functionality. If your app has it's own blocking/do not disturb system built in, you can also maintain that list server-side and not send pushes to the blocked devices. If you absolutely need to do "local" call blocking, then you can report a call and then end it. The call will be briefly visible to the user, but you can also configure the source of the call to communicate what's going on ("Blocked Call...").


4) Using VoIP pushes to trigger syncs or other non-VoIP use cases


VoIP pushes were always intended to specifically support call notifications and nothing else. The good news here is that using Notification Service Extension is the best substitute for most of the functionality that you previously handled with PushKit:


https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension


A few examples:

  • For general messaging, you can connect to your server in the extension delegate, download any missed messages, and then update the notification content as appropriate.
  • The same approach can be used to tell the user about calls missed while the device was offline and/or pending voicemails.
  • For non-user facing functionality, like data synchronization or other app maintenance, I would recommend checking out the new BackgroundTasks framework:


https://developer.apple.com/documentation/backgroundtasks


Kevin Elliott

Developer Technical Support

Core OS/Hardware

Hi Kevin, thank you for your clarifications.


Can you please confirm that this requirement will only be for apps built against the iOS 13 SDK?

Existing apps, built with the iOS 12 SDK will work as before when running on iOS 13?


Thanks in advance

I have one more question not covered here yet: What about calls showing up in system's Phone app in Recents?

What if we immediately show CallKit UI with a placeholder "Connecting Call..." in place of the caller's name, but then end the call before the user answers, either because the call already ended or whatever other reason (in our app, calls can only be received if user is connected to his home's WiFi)? Won't there be a "Connecting Call..." in recent calls in system app that we can't even call back to because we never got the number of the person who could have been calling us?

3) Block-lists/Do Not Disturb

...

If you absolutely need to do "local" call blocking, then you can report a call and then end it. The call will be briefly visible to the user, but you can also configure the source of the call to communicate what's going on ("Blocked Call...").


Is that a good practice?Sound really bad.


Two more questions:

1.My device receive a native incoming call and the call is stil ringing, then another voip call come in, And I need to report new incoming call according to the 707 session, what will happen? In my test, sometimes callkit screen present a "Call Failed" alert.

2. My app support mutiple calls(2 calls), when the 3rd call comes, push server still sends me incoming call push, what should I do, report the call and hang it up?

3.And our app also support meetings. And we don't support call and meeting in the same time. So when I'm in call status, meeting comes in, I also need to report it and hang it up. ***???


Seriously,

"Repeatedly failing to report calls may prevent your app from receiving any more incoming call notifications".

This make everything worse.

In my opinion those changes are a step back in the user experience. So far, the CallKit was greatly above its competitiors, namely android since it is highly fractured and their VoIP call handling is messy and causes a lot of bugs. However, I'm affraid these changes will cause situations where the user may think their application is bugged when in fact it is caused by API changes.


"If the user is particularly fast at tapping the accept call button, or if the network conditions are poor or there is otherwise a lot of latency, then you should simply wait until the necessary handshakes are complete before calling fulfill on the CXAnswerCallAction. To the user, it simply appears that the call is taking some time to connect, which is a common experience even with standard phone calls"


This is true IF the phone is locked. However, since every application has to implement a UI for the incoming calls (as the CallKit/native UI only remains active in lock state) a situation may occour where a user is using the phone, receives a call, picks the call up when in fact the VoIP call is not even connected, which will possibly cause the user to think it was a "ghost call", since nothing happens except (probably) the VoIP application opens up to the normal screen until the actual call arrives and triggers the incoming call UI.


I know this may seem an extreme case but in several places, ex: in Portugal, where there is not 4G cover everywhere and some even cause a fallback to GSM, this may me more common than the standard user experience.


I believe these changes only make sense IF the CallKit also fully enabled the native UI either for incoming or outgoing calls which would not lead to UI transitions and hide the fact that we are notifying the user for a call that might not have arrived yet.

This is an extremely disappointing change that WILL result in degraded user experience. We're developing an XMPP application, and this affects us greatly. We even added an honest VoIP functionality to have legal access to silent VoIP notifications, because standard silent push notifications are extremely unreliable and bad.

Hi! Voip-push started an app on ios12. What do I need to do to keep the same behavior on iOS 13? Do I need to add an apns-expiration<=5 parameter to the push payload and that's it?

I'm trying my best to Implement these new rules for VoIP Pushes.. One problem I'm really struggling with is as follows....


Our VoIP App has the capability to "Sign out".. So if you do not want to receive business calls at night time you can "Sign out"..


Currently if we are signed out then if a VoIP PUSH arrives from the server we simply ignore it. But with the new rules that would cause Apple to kill the App and prevent other pushes arriving


Ideally we would prevent the Server from sending the VoIP Pushes but that requires the server to know that we have "Signed out" which may not be possible if we "signed out" when not in 4G or Wifi coverage?


So if the user signed out when not in 4G/WiFi coverage (and so Server does not know this) then later when back in coverage a PUSH could arrive which forces us to present a call to Callkit. really annoying a user.


When signed out could I somehow "Unregister for Pushes" to avoid the rule breaking... How would I do that? Can I just set voipRegistry.desiredPushTypes to Nil and then set it back to voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP] when I sign back in ?

We are experiencing the same "Call Failed" alert when a native GSM call arrives at the same time our app is handling the CallKit screen. If the native call arrives two seconds after the CallKit screen has been dismissed everything is ok.


We have tried to replicate the same behaviour using other apps that utilise CallKit such as Whatsapp and MS Teams and they handle the inbound calls with out a problem. What are we doing wrong here??