Hello,
I’m developing a third-party VoIP app called Heyno and trying to support Siri-initiated calls so they behave like WhatsApp / FaceTime, especially from the lock screen.
Target behavior
From the locked device, the user says:
“Hey Siri, call <contact> using Heyno”
Expected result:
• System CallKit audio-call UI appears.
• No “continue in <app>” sheet, no forced unlock or foregrounding.
• Our app handles the VoIP leg in the background via CXProviderDelegate.
WhatsApp already does this with:
“Hey Siri, call <contact> on WhatsApp”
I’m trying to reproduce that behavior for Heyno using public APIs.
I have followed the SiriKit + CallKit VoIP docs but cannot get a clean Siri → CallKit → app flow from the lock screen without either:
- Being forced into .continueInApp (unlock + foreground), or
- Hitting CallKit transaction errors when starting the call from the app in response to the intent.
Current implementation
- Intents extension (INStartCallIntentHandling)
• resolveContacts(for:with:) normalizes to E.164 and returns INPersonResolutionResult.success.
• resolveDestinationType → .success(.normal).
• resolveCallCapability → .success(.audioCall).
Confirm / handle currently:
func confirm(intent: INStartCallIntent,
completion: @escaping (INStartCallIntentResponse) -> Void) {
completion(INStartCallIntentResponse(code: .ready, userActivity: nil))
}
func handle(intent: INStartCallIntent,
completion: @escaping (INStartCallIntentResponse) -> Void) {
completion(INStartCallIntentResponse(code: .ready, userActivity: nil))
}
Earlier, I used .continueInApp with an NSUserActivity carrying the normalized number and metadata, but that always produced a “Continue in Heyno” sheet that requires unlock and foreground, which breaks the lock-screen Siri flow.
- App target – CallKit provider
In the app I have CXProvider + CXProviderDelegate, which work correctly when calls are initiated from inside the app:
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
let handle = action.handle.value
// Start VoIP / WebRTC / LiveKit / Asterisk call here
provider.reportOutgoingCall(with: action.callUUID,
startedConnectingAt: Date())
provider.reportOutgoingCall(with: action.callUUID,
connectedAt: Date())
action.fulfill()
}
If I construct a CXStartCallAction and submit it via CXCallController.request(...) from the app, CallKit UI appears and our pipeline runs correctly.
What I tried and what fails
- Starting CallKit from the Intents extension
Calling CXCallController.request(...) directly from handle(intent:completion:) in the extension always yields:
com.apple.CallKit.error.requesttransaction error 1 (unentitled)
The extension does not have the CallKit entitlement, and the docs say not to initiate calls from the extension, so this path seems unsupported.
- Using .continueInApp + NSUserActivity
Pattern:
• handle(intent:) builds NSUserActivity (activityType = NSStringFromClass(INStartCallIntent.self), title = "Heyno Start Call", userInfo with E.164 handle, etc.).
• Returns INStartCallIntentResponse(code: .continueInApp, userActivity: activity).
• App receives the activity, then starts CallKit + VoIP.
Functionally this works, but iOS always requires unlock + foreground (“Continue in Heyno”), which is not acceptable for a Siri lock-screen call.
- App group + Darwin notification (extension → app → CallKit)
Experiment:
• Extension writes the normalized number into an app-group UserDefaults.
• Extension posts a Darwin notification.
• App (if running) listens, reads the number, and initiates CXStartCallAction + VoIP.
Observed:
• Works only when the app is already running in the background; a killed app is not woken.
• In some states I see CXErrorCodeRequestTransactionError.invalidAction (error 6) if I try to issue a CXStartCallAction while CallKit is already doing something as part of the Siri flow.
• Siri sometimes replies “There was a problem with the app,” likely because CallKit rejects the transaction or sees duplicate/conflicting actions.
My understanding so far
• The Intents extension should resolve/confirm the intent but not start the call.
• The source of truth for starting a call should be:
Siri → CallKit → app’s CXProviderDelegate.provider(_:perform: CXStartCallAction)
• The app then starts the VoIP leg, reports started/connected, and fulfills.
Where I am stuck
What is not clear is how Siri is supposed to route an INStartCallIntent into CallKit for a third-party VoIP app on a locked device without using .continueInApp.
If my extension simply:
• resolves the contact,
• confirm → .ready,
• handle → .ready (no NSUserActivity, no CallKit),
I do not see a documented mechanism that causes:
“Hey Siri, call <contact> using Heyno”
on the lock screen to:
• Present a CallKit audio call bound to Heyno, and
• Deliver CXStartCallAction to my CXProviderDelegate while the app stays in the background.
Questions
-
For third-party VoIP apps today, is it recommended to implement INStartCallIntentHandling at all, or should we rely only on CallKit registration and Siri’s built-in support for “Call <contact> with <app>” (no SiriKit extension)?
-
If an INStartCallIntentHandling extension is still the intended pattern: • Should confirm/handle simply return .ready and never start CallKit or set NSUserActivity?
• In that case, is Siri expected to invoke CallKit on our behalf and create a CXStartCallAction targeting our provider, even when the device is locked and the app is not foreground? -
Is there any supported way for a Siri-triggered third-party VoIP call to start from the lock screen via CallKit without: • using .continueInApp (unlock + foreground), and
• starting CallKit directly from the Intents extension (unentitled)? -
Is there any additional configuration, entitlement, provisioning profile flag, or Info.plist key required so that Siri can map “Call <contact> using Heyno” directly to our CallKit provider and background VoIP implementation?
Current options:
• .continueInApp + NSUserActivity → works, but always requires unlock + app UI.
• Start CallKit from the extension → fails with “unentitled” and appears unsupported.
• Extension → app-group + notification → app → CallKit → VoIP → fragile, with intermittent CXErrorCodeRequestTransactionError.invalidAction.
• Remove the extension and hope Siri/CallKit auto-routes to our provider → unclear if this is supported for third-party VoIP apps or reserved for privileged apps.
I would appreciate guidance on the intended architecture for this scenario, and whether the “Siri from lock screen → CallKit UI → background VoIP call” flow is achievable for an App Store VoIP app like Heyno using public APIs only.