Starting in iOS 26, two notable changes have been made to CallKit, LiveCommunicationKit, and the PushToTalk framework:
As a diagnostic aid, we're introducing new dialogs to warn apps of voip push related issue, for example when they fail to report a call or when when voip push delivery stops. The specific details of that behavior are still being determined and are likely to change over time, however, the critical point here is that these alerts are only intended to help developers debug and improve their app. Because of that, they're specifically tied to development and TestFlight signed builds, so the alert dialogs will not appear for customers running app store builds. The existing termination/crashes will still occur, but the new warning alerts will not appear.
As PushToTalk developers have previously been warned, the last unrestricted PushKit entitlement ("com.apple.developer.pushkit.unrestricted-voip.ptt") has been disabled in the iOS 26 SDK. ALL apps that link against the iOS 26 SDK which receive a voip push through PushKit and which fail to report a call to CallKit will be now be terminated by the system, as the API contract has long specified.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware
CallKit
RSS for tagDisplay the system-calling UI for your app’s VoIP services and coordinate your calling services with other apps and the system using CallKit.
Posts under CallKit tag
146 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hello,
I am developing an internal phone application using CallKit.
I am experiencing an issue with the behavior of remoteHandle settings in iOS 26 and would appreciate any insights you can provide towards a solution.
1. Problem Description
When an iPhone running iOS 26 is in a sleep state and receives a VoIP incoming call where remoteHandle is set to nil or an empty string (@""), we are unable to transition to our application (the UIExtension provided by the provider) from the CallKit UI's "More" (…) button after answering the call.
2. Conditions and Symptoms
OS Version: iOS 26
Initial State: iPhone is in a sleep state
Call Type: An unsolicited(unknown number) VoIP incoming call where the CXCallUpdate's remoteHandle is set to either nil or [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@""]
Symptoms: After answering the VoIP call by sliding the button, selecting the "More" (…) button displayed on the CallKit screen does not launch our application's UIExtension (custom UI), and the iPhone instead stay to the CallKit screen.
3. Previous Behavior (Up to iOS 18)
Up to iOS 18, even when remoteHandle was set to an empty string using the following code, the application would transition normally from "More" after answering an incoming call from a sleep state.
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@""];
[provider reportNewIncomingCallWithUUID:uuid update:update completion:completion];
4. Unsuccessful Attempts to Resolve
The issue remained unresolved after changing the handling for unsolicited(unknown number) incoming calls as follows:
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = nil; // Set remoteHandle to nil
[provider reportNewIncomingCallWithUUID:uuid update:update completion:completion];
5. Workaround (Temporary)
The problem can be resolved, and the application can transition successfully, by setting a dummy numerical value (e.g., "0") for the value in remoteHandle using the following code:
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:@"0"]; // Set a dummy numerical value
[provider reportNewIncomingCallWithUUID:uuid update:update completion:completion];
6. Additional Information
If remoteHandle is correctly set with the caller's number (i.e., not an unsolicited(unknown number) call; e.g., value:@"1234567890"), the application transitions normally from the "More" button after answering an incoming call from a sleep state, even in iOS 26.
The above issue does not occur when answering incoming calls while the iPhone is in an active state (not sleeping).
7. Questions
Have there been any other reports of similar behavior?
Should this be considered a bug in CallKit for iOS 26? Should I make file a new Feedback report?
Is there a suitable method to resolve this issue when the caller ID is unsolicited (nil or an empty string)?
This problem significantly impacts user operations as end-users are unable to perform essential in-app actions such as hold or transfer after answering an unsolicited(unknown number) call from a sleep state. We are eager to find an urgent solution and would appreciate any information or advice you can provide.
Thank you for your assistance.
Hi everyone,
I’ve filed a Feedback report (FB20986470) for a serious issue affecting the Call Directory database when add phone numbers for call blocking.
When adding blocking numbers to a Call Directory extension, the system’s CallKit database (/private/var/mobile/Library/CallDirectory/CallDirectory.db) becomes corrupted.
The reload call (reloadExtensionWithIdentifier) fails with error code 11 when the system tries to insert blocking entries, and the Console app on macOS shows the following errors:
database corruption page 2265525 of /private/var/mobile/Library/CallDirectory/CallDirectory.db at line 81343 of [f0ca7bba1c]
database corruption at line 79387 of [f0ca7bba1c]
Error Domain=com.apple.callkit.database.sqlite Code=11 "sqlite3_step for query 'INSERT INTO PhoneNumberBlockingEntry (extension_id, phone_number_id) VALUES (?, (SELECT id FROM PhoneNumber WHERE (number = ?))), (?, (SELECT id FROM PhoneNumber WHERE (number = ?))),...)'"
After this happens, CallKit becomes fully corrupted on the device and no further numbers can be added, even after:
Disabling and re-enabling the extension
Restarting the device (either force or soft restart)
Reinstalling the app
Waiting for a couple of minutes after this issue happens (that CallKit could possibly self-recovered)
I also tested other call-blocking apps, and they all fail with the same error. The only thing that recovers the system is a full “Reset All Settings.”
This issue has been reported by many users of my app, across multiple iOS versions and devices.
Similar related issue reported by another developer:
https://developer.apple.com/forums/thread/806129
Steps to Reproduce:
Enable the Call Directory extension from a call-blocking app.
Add and reload blocking numbers (a few thousand entries).
Perform multiple reloads between additions.
Check the Console, the corruption errors appear.
From this point, all insert attempts fail system-wide.
Expected Result:
Entries should be inserted successfully, or the system should self-recover without persistent corruption.
Actual Result:
sqlite3_step fails with Code=11, and the Call Directory database remains corrupted until the user resets all settings.
Additional Notes:
All numbers are sorted and deduplicated before insertion.
Happens intermittently after multiple reloads.
The system log always shows internal database failure.
Environment:
Device: iPhone 16 Plus
iOS 18.2 Beta (23C5027f)
Xcode 16.1 (17B55)
Attachments (included in Feedback FB20986470):
sysdiagnose captured immediately after the failure (with Phone app General Profile)
It seems like a system-level corruption affecting all Call Directory extensions once it occurs.
Hi, happy new year, I'm a Product Manager for a communications app that's currently in testflight. We requested the com.apple.developer.usernotifications.filtering entitlement on December 3rd, and have yet to receive a response from Apple. I understand that the holiday break may have gotten in the way, however it feels like we were lost in the queue as it's been 6 weeks with no response. Our app owner has checked-in inside appstoreconnect but has not received anything back.
Is this common? Is there any process for getting a status update?
Are we doing something wrong?
Without this entitlement we cannot make the device ring in the background. The app is a voice and video messaging platform.
Hi all,
I'm working on a Call Directory Extension using CXCallDirectoryExtensionContext. I want to add a list of numbers to be blocked. Here's the function I use:
override func beginRequest(with context: CXCallDirectoryExtensionContext) {
context.delegate = self
let blockedNumbers = loadNumberEntries(forKey: blockedKey)
let identifiedNumbers = loadNumberEntries(forKey: identifiedKey)
addAllBlocking(blockedNumbers, to: context)
addAllIdentification(identifiedNumbers, to: context)
context.completeRequest()
}
private func addAllBlocking(_ entries: [NumberEntry], to context: CXCallDirectoryExtensionContext) {
let numbers: [Int64] = entries.compactMap {
Int64($0.countryCode + $0.phone)
}.sorted()
for number in numbers {
context.addBlockingEntry(withNextSequentialPhoneNumber: number)
print("# Added blocking entry: \(number)")
}
}
When I run this, I see in the console:
# Added blocking entry: (*my number with country code*)
So it seems the number is added correctly. However, in practice, the number is not blocked on the device.
I’ve made sure that:
The number is stored with the country code prefix.
The extension is enabled in Settings → Phone → Call Blocking & Identification.
The extension is reloaded after adding numbers.
The array of numbers is sorted in ascending order before calling addBlockingEntry.
Despite all this, the number still isn’t blocked.
Does anyone know why the print shows the number added, but it doesn’t actually block the call? Am I missing something in the way CXCallDirectoryExtensionContext works?
Thanks for any advice!
I try to update remoteHandle using CXCallUpdate for outgoing call, but this works only on iOS 15 but not on 17 or 18 (16 didn't test). This problem actual only for outgoing calls, but for incoming calls update works fine.
func startOutgoingCall(with callID: UUID, userID: String) {
let handle = CXHandle(type: .generic, value: userID)
let action = CXStartCallAction(call: callID, handle: handle)
callController.requestTransaction(with: action) { [weak self] error in
// ...
}
}
func updateOutgoingCall(with callID: UUID, groupID: String) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: groupID)
provider.reportCall(with: callID, updated: update)
}
I also tried phoneNumber type but it seems initial handle that I set to CXStartCallAction not possible to change (value or even type).
I use this handle value to implement recall by tap on call in Recents tab of system address book. But since my calls can transform from p2p to group call, I need to update handle value or find some another way to pass call identification info.
In my application, I use CallKit and have supportsHolding = true set. During my phone call, another call comes in (e.g., GSM). I accept the incoming call and put the current call on hold.
If I end the active call myself, everything is fine, and CallKit calls the
method provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession).
However, if the other party ends the call, the second call remains on hold. In the application, the user clicks on unhold, and I notify CallKit that the hold has ended.
But in this case, the didActivate method is not called at all. If I try to activate the audio myself after unhold, I receive the error:
Domain=NSOSStatusErrorDomain Code=561017449 "Session activation failed" UserInfo={NSLocalizedDescription=Session activation failed}
AVAudioSessionErrorInsufficientPriority == NSOSStatusErrorDomain Code: 561017449
What needs to be done for CallKit to activate my audio?
I can reproduce the bug that CallKit doesn't active audiosession after the outgoing call put on hold because of an incoming call.
VoIP calling with CallKit
Steps to reproduce:
Download SpeakerBox example app from the link above and start it with XCode
Start a new outgoing call
Call your phone from other phone
Hold and Accept the call
After a few secs finish the call from the other phone
The outgoing call will be still on hold
Click on the call and click Toggle Hold
The call won't be active again because the audiosession is activated.
Logs:
Received provider(_:didDeactivate:)
Received provider(_:didDeactivate:)
Received provider(_:didDeactivate:)
Received provider(_:didDeactivate:)
Received provider(_:didDeactivate:)
Requested transaction successfully
Starting audio
Type: stdio
AURemoteIO.cpp:1162 failed: 561017449 (enable 3, outf< 1 ch, 44100 Hz, Float32> inf< 1 ch, 44100 Hz, Float32>)
Type: Error | Timestamp: 2024-08-15 12:20:29.949437+02:00 | Process: Speakerbox | Library: libEmbeddedSystemAUs.dylib | Subsystem: com.apple.coreaudio | Category: aurioc | TID: 0x19540d
AVAEInternal.h:109 [AVAudioEngineGraph.mm:1344:Initialize: (err = PerformCommand(*outputNode, kAUInitialize, NULL, 0)): error 561017449
Type: Error | Timestamp: 2024-08-15 12:20:29.949619+02:00 | Process: Speakerbox | Library: AVFAudio | Subsystem: com.apple.avfaudio | Category: avae | TID: 0x19540d
Couldn't start Apple Voice Processing IO: Error Domain=com.apple.coreaudio.avfaudio Code=561017449 "(null)" UserInfo={failed call=err = PerformCommand(*outputNode, kAUInitialize, NULL, 0)}
Type: Notice | Timestamp: 2024-08-15 12:20:29.949730+02:00 | Process: Speakerbox | Library: Speakerbox | TID: 0x19540d
Route change:
Type: Notice | Timestamp: 2024-08-15 12:20:30.167498+02:00 | Process: Speakerbox | Library: Speakerbox | TID: 0x19540d
ReasonUnknown
Type: Notice | Timestamp: 2024-08-15 12:20:30.167549+02:00 | Process: Speakerbox | Library: Speakerbox | TID: 0x19540d
Previous route:
Type: Notice | Timestamp: 2024-08-15 12:20:30.167568+02:00 | Process: Speakerbox | Library: Speakerbox | TID: 0x19540d
<AVAudioSessionRouteDescription: 0x302c00bc0,
inputs = (
"<AVAudioSessionPortDescription: 0x302c01330, type = MicrophoneBuiltIn; name = iPhone Mikrofon; UID = Built-In Microphone; selectedDataSource = (null)>"
);
outputs = (
"<AVAudioSessionPortDescription: 0x302c004d0, type = Receiver; name = Vev\U0151; UID = Built-In Receiver; selectedDataSource = (null)>"
)>
Type: Notice | Timestamp: 2024-08-15 12:20:30.167626+02:00 | Process: Speakerbox | Library: Speakerbox | TID: 0x19540d
I’m developing an iOS app that integrates with CallKit.
Starting from iOS 26, I’ve noticed that the system automatically presents a top banner / toast-style UI when a CallKit call becomes active (see attached screenshot). This UI appears to be fully managed by the system.
On iOS versions prior to iOS 26, this UI did not appear under the same CallKit configuration.
What I’ve observed
The banner is displayed automatically by the system
It appears at the top of the screen, similar to a toast or call status banner
It is not a view created by my app
I could not find any public API or CallKit configuration related to dismissing or controlling it
My questions:
Is this top banner an intended system behavior change in newer iOS versions?
Is there any public API to dismiss, hide, or customize this UI?
If not, is this UI considered non-dismissible by design?
Any clarification on the expected behavior or recommended approach would be greatly appreciated.
Hi,
We've noticed that this issue occurs more frequently after upgrading to iOS 18.4.1 and can result in one-way audio.
Our app uses CallKit with WebRTC to establish VoIP connections.
However, on iOS 18.4.1, CallKit no longer triggers:
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession)
We're currently comparing the occurrence rate across different iOS versions to better understand the impact.
Could you please help analyze the root cause of this issue?
I'm developing a VoIP app.
Currently, I'm using CallKit to control call acceptance, and end-of-call processing.
When a call comes in while using the phone, CallKit appears as a banner at the top of the screen.
When I click "Accept" on the banner, the app opens and the call is received. (For OEMs, clicking the "Accept" button in the banner will accept the call as is.)
The reason this feature is needed is, for example, when a call comes in as a banner call while using a navigation app, accepting the call causes the navigation app to go to the back and the VoIP app to come to the foreground, causing inconvenience to customers. This needs to be improved.
Please advise.
I am facing a severe audio routing instability issue when using CallKit and the Zego Express SDK on iOS. The problem is that the audio route immediately reverts from the Speaker back to the Earpiece, effectively disabling the Speaker button functionality.📝 Observed BehaviorWhen the user taps the native CallKit Speaker Button, the audio route is correctly changed to the Speaker, but then instantly flips back to the Receiver (Earpiece), as shown in the system log captured via AVAudioSession.routeChangeNotification monitoring.🧾 Log Evidence (Flapping Occurs in 0.4 seconds)The following log snippet clearly illustrates the system overriding the user's action (reason: 4) with an unexpected CategoryChange (reason: 3) event:
TimestampComponentReason CodeDescriptionRouteIs
Speaker16:31:18.009[CallKitManager]4Override (CallKit/ControlCenter)Loa ngoài
(Speaker)true16:31:18.411[CallKitManager]3CategoryChangeThiết bị nhận (Receiver)false
Hello, colleagues.
I am reaching out to the community with an issue that is likely quite common. Unfortunately, I have not been able to resolve it independently and would like to confirm if my approach is correct.
Core Problem: VoIP push notifications are not delivered to the application when it is in the background or terminated. After reviewing existing discussions on the forum, I concluded that the cause might be related to CallKit not having enough time to register. However, in my case, I am using AppDelegate, and in theory, CallKit registration should occur quickly enough. Nevertheless, the issue persists.
Additional Question: I would also like to clarify the possibility of customizing the CallKit interface. Specifically, I am interested in adding custom buttons (for example, to open a door). Please advise if such functionality is supported by CallKit itself, or if a different approach is required for its implementation.
Thank you in advance for your time and attention to my questions. For a more detailed analysis, I have attached a fragment of my code below. I hope this will help clarify the situation.
Main File
@main
struct smartappApp: App {
@UIApplicationDelegateAdaptor private var delegate: AppDelegate
@StateObject private var navigationManager = NavigationManager()
init() {
print("Xct")
if !isRunningTests() {
DILocator.instance
.registerModules([
ConfigModule(),
SipModule(),
AuthModule(),
NetworkModule(),
CoreDataModule(),
RepositoryModule(),
DataUseCaseModule(),
SipUseCaseModule()
])
}
}
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
print("Axtest")
completionHandler(.newData)
}
var body: some Scene {
App Delegate File
class AppDelegate: UIResponder, UIApplicationDelegate {
private let voipRegistry = PKPushRegistry(queue: .main)
private var provider: CXProvider?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setupPushKit()
setupCallKit()
return true
}
public func regist(){
setupPushKit()
setupCallKit()
}
private func setupPushKit() {
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [.voIP]
DispatchQueue.main.async {
self.voipRegistry.pushToken(for: .voIP)
}
}
// MARK: - CallKit Setup
private func setupCallKit() {
let configuration = CXProviderConfiguration(localizedName: "MyApp")
configuration.maximumCallGroups = 1
configuration.maximumCallsPerCallGroup = 1
configuration.supportsVideo = true
configuration.iconTemplateImageData = UIImage(named: "callIcon")?.pngData()
provider = CXProvider(configuration: configuration)
provider?.setDelegate(self, queue: .main)
}
// MARK: - Background Launch Handling
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
print("Axtest")
completionHandler(.newData)
}
}
extension AppDelegate: PKPushRegistryDelegate {
func pushRegistry(_ registry: PKPushRegistry,
didUpdate pushCredentials: PKPushCredentials,
for type: PKPushType) {
APNsImpl().registerToken(token: pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined())
}
func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType) {
guard type == .voIP else {
return
}
print("call kit")
let payloadDict = payload.dictionaryPayload
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: "dsfsdfddsf")
update.hasVideo = payloadDict["hasVideo"] as? Bool ?? false
provider?.reportNewIncomingCall(with: UUID(),
update: update,
completion: { error in
if let error = error {
print("Failed to report incoming call: \(error.localizedDescription)")
} else {
print("Successfully reported incoming call")
}
})
}
}
We tested call blocking on iOS 26 and noticed something strange: the call will not be blocked if an outgoing call was made to its number before. Nevertheless, it will be blocked if we delete the outgoing call record from the Phone.app Recents.
This behavior looks like a bug and is unexpected when using our application. Was this a planned callkit change in iOS 26? Is it possible to get the correct call blocking behavior back?
We set blocking rules with addBlockingEntry(withNextSequentialPhoneNumber:) and this problem is not present in iOS 18 and earlier.
Thank you in advance
Hello,
I recently had an unusual experience, and I’m wondering if this is related to Apple’s policies, so I wanted to ask.
While a call is in Picture-in-Picture (PIP) mode, notification pushes from the same app do not appear.
The API is being triggered, but the notification banner does not show on the device.
Once PIP is closed, the notifications start appearing normally again.
Is this behavior enforced by Apple’s policies?
What’s interesting is that banners from other apps do appear — only the banners from the app currently in PIP are not shown.
In iOS 26.1, after my app answers a VoIP call on the lock screen, tapping the bottom-left "More" button doesn't bring up the app icon to jump to the app. The same scenario works normally on iOS 26. How can I resolve this issue?
In my case, when I try to block calls on iOS 26, the blocking doesn't occur; the scenarios seem intermittent. If I create two CallDirectory extensions, the first blocks the numbers, but the second doesn't. Interestingly, the extension marks the number as suspicious. There's also a case where, on iOS 26 on an iPhone 16 Pro, the functionality doesn't work at all. I'd like to know if there have been any changes to the use of CallKit in iOS 26, because users of my app on iOS 18 and below report successful blocking.
Rejecting a Cellular Call Also Rejects Application Video Call
Steps:
1.When a user receives a cellular call and it is in the "ringing" state and application receives a video call which is reported to CallKit
2.User rejects the Cellular call from Callkit UI, Video call is also getting rejected.
3.Application is receiving performEndCallAction when user is rejecting the Cellular call
As a part of CXcallobserver Application is receiving call connected then disconnected for the cellular call
Irrespective of OS, the issue only reproduces on cellular calls if Live Voicemail is enabled.
Issue is not reproduced when Live Voicemail is disabled for cellular calls, and it is not reproducing on FaceTime calls, regardless of the Live Voicemail setting.
This results in a poor user experience because:
The recipient unintentionally misses the CallKit-reported call.
The initiator receives confusing and inaccurate status information, believing the recipient is busy rather than having chosen to decline the pending video call.
Our app receives a CallKit VoIP call. When the user taps “Answer”, the app launches and automatically connects to a real-time audio session using WebRTC or MobileRTC.
We would like to confirm whether the following flow (“CallKit Answer → app opens → automatic WebRTC or MobileRTC audio session connection”) complies with Apple’s VoIP Push / CallKit policy.
In addition, our service also provides real-time video-class functionality using the Zoom Meeting SDK (MobileRTC). When an incoming CallKit VoIP call is answered, the app launches and the user is automatically taken to the Zoom-based video lesson flow: the app opens → the user is landed on the Zoom Meeting pre-meeting room → MobileRTC initializes immediately. In the pre-meeting room, audio and video streams can already be active and MobileRTC establishes a connection, but the actual meeting screen is not joined until the user explicitly taps “Join”. We would like to confirm whether this flow for video lessons (“CallKit Answer → app opens → pre-meeting room (audio/video active) → user taps ‘Join’ → enter actual meeting”) is also compliant with Apple’s VoIP Push and CallKit policy.
I read online that there is no way to extract the call log from an iPhone. I want to develop an app to help people remember to call their mom, and if they did, the "nagging" would disappear automatically. I'm looking for any workaround to know when a user called someone, without having them log it manually.