Questions about VoIP Push compliance rules and CallKit handling

Hello everyone,

I’m an iOS developer working on a real-time communication app that supports VoIP calls using CallKit. The app has been in production for more than 5 years.

Over the years, some users have occasionally reported that they do not receive incoming call pushes. We have tried multiple optimizations on both the client and server side, but the improvement has been limited.

From Apple documentation and discussions online, I understand that iOS may restrict VoIP pushes if the system detects violations of VoIP push usage rules (for example, not presenting a CallKit call after receiving a VoIP push). However, the exact rules and thresholds for these violations are not clearly documented, so I’d like to ask a few questions to better understand the expected behavior.

Below is a simplified description of our current call flow. Call Flow Caller

When the user initiates a call:

We do not use CallKit

The call is handled entirely using a custom in-app call UI

Callee

When the user receives a call:

  1. Device locked or app in background

A VoIP push wakes the app

The app presents the CallKit incoming call UI

  1. App in foreground

The server still sends a VoIP push

The app first reports the call to CallKit

After a very short delay, the app programmatically ends the CallKit call

Then a custom in-app call UI is presented via the app's long connection

The reason we always send a VoIP push (even when the app is in the foreground) is that we want to maximize call delivery reliability.

We do not use CallKit. The call is handled entirely using a custom in-app call UI.

This is a bad idea. It's going to significantly complicate your app’s audio logic and, most practically, it means that ANY other incoming call from ANY CallKit app will IMMEDIATELY interrupt your app’s audio. It's also going to allow a variety of other odd edge cases which will be similarly disruptive (for example, now playing activity can interrupt your calls). The PRIMARY reason CallKit was originally created was so the system could properly arbitrate and manage the audio of VoIP apps, preventing all those edge cases.

However, the exact rules and thresholds for these violations are not clearly documented, so I’d like to ask a few questions to better understand the expected behavior.

The basic policy rule is that your app is not required to report a call if it either:

  1. Is already on a call.

  2. Is in the foreground.

However, in practice, both of those criteria are much harder to implement than they seem. The problem here is that your app is managed by callservicesd, so what actually matters is what state IT thinks your app is in, NOT what state YOUR app thinks it's in.

Going forward (iOS 26.4+), we've introduced an API that "properly" addresses these issues. The details are in this forum post, but the new API formally tells your app whether or not you must report a call. Note that this new delegate both addresses the two cases above and will also allow you to discard pushes which are "old enough" that they're unlikely to still be valid. I'll talk about your options on older systems below, but you should also adopt the new delegate as soon as possible and rely on its guidance instead. Note that the old delegate will be ignored on older systems, so you can include it "now" without worrying about it changing anything on your older systems.

Shifting to older systems:

If you're already on a call (#1), then the right way to handle this case is to report a new call using the UUID of your existing call. In that case, one of two things will happen:

  1. If you're still on a call, then your new call report will fail with a duplicate UUID error. Nothing will be visible to the user, nor will your app be "penalized" or otherwise disrupted.

  2. If the existing call happens to have ended, then your new call report will occur, which you'll then have to end. This does mean the call will be briefly visible to the user, but that's better than crashing.

Critically, the flow above is entirely "safe", as both cases meet the requirements of our API contract.

Shifting to foreground handling, there isn't any approach that I can guarantee will be safe. Just like call changes, app transitions are asynchronous, so it's entirely possible for your app to be "in the background" but not yet "know" that's the case. All I can really suggest here is that you use the main thread for push delivery (not a background thread) and that you minimize main thread activity. Together, that minimizes the opportunity that your app’s state will be out of date, which is the best you can do to reduce the risk of being terminated.

thresholds for these violations are not clearly documented

We've never formally documented the exact threshold, but if you experiment a bit, you'll find that delivery will stop after ~5 reporting failures within ~24 hours. Note that deleting and reinstalling the app will reset the limit if you want to test this. Also, the debugger disables this termination logic, so you can test this with Xcode.

__
Kevin Elliott

Questions about VoIP Push compliance rules and CallKit handling
 
 
Q