callkit
Implement VoIP calling with CallKit and PushKit. Use when building incoming/outgoing call flows, registering for VoIP push notifications, configuring CXProvider and CXCallController, handling call actions, coordinating audio sessions, or creating Call Directory extensions for caller ID and call bloc
By dpearson2699 · 2,631 installs
npx skills add dpearson2699/swift-ios-skills --skill callkit
Source repository · Upstream listing
CallKit
Build VoIP calling features that integrate with the native iOS call UI using
CallKit and PushKit. Covers incoming/outgoing call flows, VoIP push
registration, audio session coordination, and call directory extensions.
Contents
[Setup]( setup)
[Provider Configuration]( provider configuration)
[Incoming Call Flow]( incoming call flow)
[Outgoing Call Flow]( outgoing call flow)
[PushKit VoIP Registration]( pushkit voip registration)
[Audio Session Coordination]( audio session coordination)
[Call Directory Extension and Manager]( call directory extension and manager)
[Common Mistakes]( common mistakes)
[Review Checklist]( review checklist)
[References]( references)
Setup
Project Configuration
1. Enable the Voice over IP background mode in Signing & Capabilities
2. Add the Push Notifications capability
3. For call directory extensions, add a Call Directory Extension target
Key Types
Type Role
CXProvider Reports calls to the system, receives call actions
CXCallController Requests call actions (start, end, hold, mute)
CXCallUpdate Describes call metadata (caller name, video, handle)
CXProviderDelegate Handles system call actions and audio session events
PKPushRegistry Registers for and receives VoIP push notifications
PKVoIPPushMetadata iOS 26.4+ metadata that says whether a VoIP push must be reported
Provider Configuration
Create a single CXProvider at app launch and keep it alive for the app
lifetime. Configure it with a CXProviderConfiguration that describes your
calling capabilities.
Incoming Call Flow
When a required VoIP call push arrives, report the incoming call to CallKit
immediately. The system displays the native call UI. You must report required
calls before the PushKit completion handler returns failure to do so causes
the system to terminate your app.
Handling the Answer Action
Implement CXProviderDelegate to respond when the user answers:
Outgoing Call Flow
Use CXCallController to request an outgoing call. The system routes the
request through your CXProviderDelegate .
Delegate Methods for Outgoing Calls
PushKit VoIP Registration
Register for VoIP pushes at every app launch and send token changes to your
server. For iOS 13 SDK+ apps, every report required VoIP call push must be
reported before PushKit completion using CallKit, or LiveCommunicationKit for
apps built on that framework. On iOS 26.4+, PKVoIPPushMetadata.mustReport is
the gate: true means report before completion; false means no CallKit or
LiveCommunicationKit report is required. Missing a required report before
completion can terminate the app, and repeated failures may stop VoIP delivery.
Path Report decision Completion timing
iOS 26.4+ mustReport == true Report with CallKit or LiveCommunicationKit After report callback
iOS 26.4+ mustReport == false No CallKit/LiveCommunicationKit report required After local handling
Older delegate iOS 13 SDK+ treats VoIP call pushes as report required After report callback
Server side VoIP pushes should use a short lifetime: set apns expiration to
0 or only a few seconds. After the initial push wakes the app, send hangups
and call detail changes over the app server connection instead of sending more
VoIP pushes.
Audio Session Coordination
CallKit owns the audio activation boundary: start media only in
provider( :didActivate:) , and stop or tear it down in
provider( :didDeactivate:) and reset paths.
Call Directory Extension and Manager
Use Call Directory for preloaded caller ID/blocking, not per call API lookup.
The extension loads sorted bulk data in beginRequest(with:) ; the main app uses
CXCallDirectoryManager to check enabled status, open Call Blocking &
Identification settings when disabled, and reload after data changes. Store
CXCallDirectoryPhoneNumber as country code plus digits in ascending order
(for example 18005551234 ), not a formatted string.
Main App Manager: Status, Settings, Reload
Check getEnabledStatusForExtension(...) before assuming the extension is
active, use openSettings(...) for Call Blocking & Identification when
disabled, and call reloadExtension(...) after data changes. Route APNs
auth key rotation and normal remote notification setup to push notifications.
Common Mistakes
Mistake Fix
Required VoIP push is treated as data only Apply the version specific report rule, report before completion, then invoke the PushKit completion handler.
Answer action is fulfilled before media/server readiness Keep it pending while connecting; fulfill on readiness or fail and report .failed .
Media starts before provider( :didActivate:) Prepare earlier if needed, but start only after activation and stop on deactivation/reset.
An action path never calls fulfill() or fail() Give success, cancellation, timeout, and network error paths one terminal action.
Token refresh is ignored Send every didUpdate pushCredentials token to the server.
Call Directory performs per call networking Preload sorted entries and reload the extension.
Review Checklist
[ ] VoIP background mode enabled in capabilities
[ ] Single CXProvider instance created at app launch and retained
[ ] CXProviderDelegate set before reporting any calls
[ ] iOS 26.4+ PushKit path reports when mustReport is true and may skip when false
[ ] iOS 13 SDK+ PushKit VoIP call pushes report to CallKit before completion
[ ] VoIP APNs requests use apns expiration of 0 or only a few seconds
[ ] Hangups and detail updates use the app server connection after the initial push
[ ] action.fulfill() or action.fail() called for every provider delegate action
[ ] CXAnswerCallAction fulfilled only after the call server/media connection is ready
[ ] Audio engine started only after provider( :didActivate:) callback
[ ] Audio engine stopped in provider( :didDeactivate:) callback
[ ] Audio session category set to .playAndRecord with .voiceChat mode
[ ] VoIP push token sent to server on every didUpdate pushCredentials callback
[ ] PKPushRegistry created at every app launch (not lazily)
[ ] Call Directory data is preloaded, not fetched per incoming call
[ ] CXCallDirectoryPhoneNumber documented as country calling code + digits
[ ] CXCallDirectoryManager names status check, reload, and settings opening APIs
[ ] CXCallUpdate populated with localizedCallerName and remoteHandle
[ ] Outgoing calls report startedConnectingAt and connectedAt timestamps
[ ] iOS 26 call translation keeps upstream audio active during mute
[ ] Encrypted metadata filtering mentions the notification service extension entitlement
References
Extended patterns (hold, mute, group calls, delegate lifecycle): [references/callkit patterns.md](references/callkit patterns.md)
[CallKit framework](https://sosumi.ai/documentation/callkit)
[CXProvider](https://sosumi.ai/documentation/callkit/cxprovider)
[CXCallController](https://sosumi.ai/documentation/callkit/cxcallcontroller)
[CXCallAction](https://sosumi.ai/documentation/callkit/cxcallaction)
[CXCallUpdate](https://sosumi.ai/documentation/callkit/cxcallupdate)
[CXProviderConfiguration](https://sosumi.ai/documentation/callkit/cxproviderconfiguration)
[CXProviderDelegate](https://sosumi.ai/documentation/callkit/cxproviderdelegate)
[PKPushRegistry](https://sosumi.ai/documentation/pushkit/pkpushregistry)
[PKPushRegistryDelegate](https://sosumi.ai/documentation/pushkit/pkpushregistrydelegate)
[PKVoIPPushMetadata](https://sosumi.ai/documentation/pushkit/pkvoippushmetadata)
[CXCallDirectoryProvider](https://sosumi.ai/documentation/callkit/cxcalldirectoryprovider)
[CXCallDirectoryPhoneNumber](https://sosumi.ai/documentation/callkit/cxcalldirectoryphonenumber)
[CXCallDirectoryManager](https://sosumi.ai/documentation/callkit/cxcalldirectorymanager)
[CXSetTranslatingCallAction](https://sosumi.ai/documentation/callkit/cxsettranslatingcallaction)
[reportNewIncomingVoIPPushPayload( :completion:)](https://sosumi.ai/documentation/callkit/cxprovider/reportnewincomingvoippushpayload( :completion:))
[Making and receiving VoIP calls](https://sosumi.ai/documentation/callkit/making and receiving voip calls)
[Responding to VoIP Notifications from PushKit](https://sosumi.ai/documentation/pushkit/responding to voip notifications from pushkit)