ionic-callkit
v1.0.0-beta.4
Published
Native call UI for Ionic/Capacitor: CallKit + PushKit on iOS, ConnectionService + FCM on Android
Maintainers
Readme
ionic-callkit
Native incoming / outgoing call UI for Ionic & Capacitor 7 apps.
| Platform | Native layer | Push |
|---|---|---|
| Android | Self-managed ConnectionService (ported from react-native-callkeep), full-screen ringing notification, phoneCall foreground service | FCM (data-only, high priority) |
| iOS | CallKit (CXProvider) | PushKit VoIP |
| Web | Emits the same events so you can drive an HTML call screen | — |
The plugin shows and tracks the call. Media is yours: start your WebRTC / SIP session on callAnswered / callStarted and stop it on callEnded.
Status: Android, iOS and web implement the API below. Android is verified on an emulator; the iOS implementation (ported from callkeep's
RNCallKeep.m) still needs its first build and device test — see WORK.md. The previous iOSplugin_eventslistener is gone; the legacyConnectionId/Usernamepush keys are still accepted.
Install
npm install ionic-callkit
npx cap syncRequires Capacitor 7, Android 8.0+ (API 26) and iOS 14+.
Android setup
- Firebase — add your app in the Firebase console and place
google-services.jsoninandroid/app/. The Capacitor Android template applies the Google Services plugin automatically when that file exists. - minSdk 26 — in
android/variables.gradlesetminSdkVersion = 26. - Permissions — the plugin manifest already declares everything self-managed mode needs (
MANAGE_OWN_CALLS,RECORD_AUDIO,FOREGROUND_SERVICE_PHONE_CALL,FOREGROUND_SERVICE_MICROPHONE,POST_NOTIFICATIONS,USE_FULL_SCREEN_INTENT, …). At runtime callrequestPermissions()for microphone + notifications. - Full-screen intent (Android 14+) — Google Play only grants
USE_FULL_SCREEN_INTENTby default to calling / alarm apps. CheckcanUseFullScreenIntent()and send the user toopenFullScreenIntentSettings()if needed; without it a locked phone shows a heads-up notification instead of the full-screen call. - Incoming-call screen — when the phone is locked or the screen is off, the ringing call opens a native full-screen call screen (caller, Answer, Decline) over the lock screen, even a PIN/biometric one; Answer then opens your app over the lock screen. When the phone is unlocked and in use, Android shows a heads-up notification with Answer/Decline instead (same as every calling app). Set
android.incomingCallScreen: 'app'to open your own call page instead of the native screen. Translate / restyle the native screen by overriding theionic_callkit_*strings and colors in your app'sres/values. - Ringtone — the plugin rings with the device ringtone and vibrates, following the phone's ringer mode (normal → sound + vibration, vibrate → vibration, silent → nothing); a volume-key press silences it. Custom sound: put a file in
android/app/src/main/res/raw/and setandroid.ringtoneSound: 'my_ring'; disable vibration withandroid.vibrate: false. - Managed mode (optional) —
setup({ android: { selfManaged: false } })uses the system dialer UI instead. AddREAD_PHONE_STATE,READ_PHONE_NUMBERSandCALL_PHONEto your app manifest, request thephonepermission, and have the user enable the calling account (openPhoneAccountSettings()).
Using it with @capacitor/push-notifications
Both plugins work together with no extra setup. Android delivers FCM to a single FirebaseMessagingService; ionic-callkit's service is declared with a higher priority, handles call pushes (type: incoming_call / call_ended) itself, and forwards every other message and token refresh to @capacitor/push-notifications, so your pushNotificationReceived / registration listeners keep firing. Both plugins see the same FCM token.
If your app has its own FirebaseMessagingService, remove the plugin's from the merged manifest and pass call messages to it:
<service android:name="io.kreador.callkit.CallMessagingService" tools:node="remove" />if (io.kreador.callkit.CallMessagingService.handleRemoteMessage(this, remoteMessage)) return;Web
Browsers have no system call UI, so on the web your app renders the call screen (e.g. an ion-modal or a /call/:id page). The web implementation tracks calls and fires the same events as native, so the same listeners drive it. Web gets no pushes: call displayIncomingCall() when your own signalling (WebSocket, LiveKit room event, …) reports a call.
// Web: your signalling reports an incoming call
await CallKit.displayIncomingCall({ callId, callerName: 'Alice' });
// → 'incomingCall' fires → the same listener opens your call pageregisterVoipToken, getAudioRoutes, setAudioRoute and the Android settings helpers reject with "not implemented" on web.
iOS setup
Xcode → Signing & Capabilities: add Push Notifications and Background Modes → Voice over IP.

Create a VoIP Services certificate on developer.apple.com/certificates, download it and import it into Keychain Access.

Export it as
.p12, then convert it for the test script:
openssl pkcs12 -in YOUR_CERTIFICATES.p12 -out app.pem -nodes -clcerts
iOS notes
- Every VoIP push must show a call. iOS 13+ terminates apps that receive a VoIP push without reporting a call to CallKit. The plugin reports calls synchronously from the push. For pushes it can't show (not a call payload,
call_endedfor an unknown call, busy) it reports and immediately ends a call, which can appear briefly or as a missed call. Prefer sendingcall_endedover your signalling or a regular (non-VoIP) push when the app is running. - Answering from the lock screen does not open the app. CallKit keeps the call in the system UI; your app is running in the background, so start media on
callAnsweredwithout relying on the WebView being visible. - Call ids: CallKit needs UUIDs. Use UUIDs as
callIdif you can; other strings work too and are mapped internally. - Early PushKit registration (recommended): call
CallKitVoip.start()inapplication(_:didFinishLaunchingWithOptions:)(import IonicCallkitwith CocoaPods,import CallKitPluginwith SPM). The plugin also registers when it loads, but registering at launch means a VoIP push that starts the app doesn't wait for the WebView. - Audio:
ios.audioSession.autoConfigure: falselets your media SDK ownAVAudioSession; otherwise the plugin configures play-and-record with Bluetooth when CallKit activates the session. - Not available on iOS:
backToForeground,openPhoneAccountSettings,openFullScreenIntentSettings(reject as unimplemented);setAvailable,setReachableare no-ops;showIncomingCallUi/silenceIncomingCall/checkReachability/hasActiveCallare Android-only events.
Usage
import { CallKit } from 'ionic-callkit';
// Register listeners first: events that happened while the app was starting
// (e.g. the user answered from the lock screen) are delivered as soon as you listen.
await CallKit.addListener('voipToken', ({ token }) => {
// POST the token to your backend (PushKit token on iOS, FCM token on Android)
});
await CallKit.addListener('incomingCall', ({ callId, callerName }) => {
router.navigate(['/call', callId]);
});
await CallKit.addListener('callAnswered', ({ callId }) => webrtc.startSession(callId));
await CallKit.addListener('callEnded', ({ callId }) => webrtc.endSession(callId));
await CallKit.addListener('callRejected', ({ callId }) => signalling.decline(callId));
await CallKit.setup({ appName: 'My App', supportsVideo: true });
await CallKit.requestPermissions();
await CallKit.registerVoipToken();Outgoing:
await CallKit.startCall({ callId, calleeName: 'Bob', handle: '[email protected]' });
// …when the remote side picks up:
await CallKit.setCallActive({ callId });
// …when it hangs up:
await CallKit.reportEndCall({ callId, reason: CallEndReason.RemoteEnded });A complete Ionic Angular demo lives in example/.
Cold starts, reachability and concurrency (Android)
- Cold start: when a push wakes a killed app, events raised before your listeners exist (
incomingCall,callAnsweredfrom the lock screen, …) are queued natively, persisted, and delivered as soon as you add listeners.getInitialEvents()also returns them (oldest first) so you can route straight to the call screen at startup; callclearInitialEvents()once handled. Events markedrestored: truecome from a previous process that died before delivering them — Android has already dropped those calls. - Reachability watchdog: if a call is answered but your app never comes up to handle it, the plugin ends it after
android.answerReachabilityTimeoutms (default 10000,0disables). Adding acallAnsweredlistener or callingsetReachable()marks the app as ready. - Call state:
setCallState({ callId, state })reportsdialing/ringing/active/heldto the system; every change is emitted ascallStateChanged. - One call at a time:
setCanMakeMultipleCalls({ allow: false })(orandroid.canMakeMultipleCalls: false) refuses a second incoming call withincomingCallFailed { error: 'busy' }and makesstartCallreject — tell the caller "busy" from that event.
Backend contract
The plugin only produces tokens; your server sends the pushes. Never ship APNs / FCM credentials in the app.
Both platforms read the same keys:
| Key | Required | Notes |
|---|---|---|
| type | no | incoming_call (default) or call_ended (caller hung up / cancelled) |
| callId | yes | Your call id; every event carries it |
| callerName | no | Shown in the call UI |
| handle | no | Number / user id; defaults to callerName |
| hasVideo | no | "true" / "false" |
The legacy keys ConnectionId / Username are still accepted.
Use a new callId per call (a UUID is ideal — iOS uses it directly). Pushes are de-duplicated per call id, and a push for a call id that ended in the last 5 minutes is ignored, so retries and a call_ended that overtakes its incoming_call never ring a finished call.
iOS — APNs VoIP push (https://api.push.apple.com/3/device/<token>, headers apns-topic: <bundle>.voip, apns-push-type: voip, apns-priority: 10):
{
"aps": { "alert": "Incoming call", "content-available": 1 },
"type": "incoming_call",
"callId": "abc-123",
"callerName": "Alice",
"hasVideo": "false"
}iOS 13+ terminates apps that receive a VoIP push without reporting a call, so only send VoIP pushes for real incoming calls.
Android — FCM HTTP v1 (POST https://fcm.googleapis.com/v1/projects/<project>/messages:send). The message must be data-only (no notification block) with high priority, otherwise it will not reach the app while it is killed:
{
"message": {
"token": "<fcm token>",
"android": { "priority": "high", "ttl": "30s" },
"data": { "type": "incoming_call", "callId": "abc-123", "callerName": "Alice", "hasVideo": "false" }
}
}Test scripts
# iOS (needs app.pem in the current directory)
dev/sendVoip.sh <callId> <voipToken> <bundleId> [hasVideo] [callerName] [sandbox|production]
# Android (service account JSON from Firebase → Project settings → Service accounts)
dev/sendFcm.sh <service-account.json> <fcmToken> [callId] [callerName] [hasVideo]
dev/sendFcm.sh <service-account.json> <fcmToken> <callId> --endFor production APNs use api.push.apple.com; sending a sandbox token there returns BadDeviceToken.
Credits
The Android implementation is a port of livekit/react-native-callkeep (ISC, © The CallKeep Authors), adapted for Capacitor.
API
setup(...)registerVoipToken()unregisterVoipToken()displayIncomingCall(...)answerCall(...)rejectCall(...)startCall(...)setCallActive(...)setCallState(...)endCall(...)endAllCalls()reportEndCall(...)updateDisplay(...)setMuted(...)setSpeaker(...)setOnHold(...)sendDTMF(...)getAudioRoutes()setAudioRoute(...)setAvailable(...)setReachable()setCanMakeMultipleCalls(...)getInitialEvents()clearInitialEvents()hasPhoneAccount()openPhoneAccountSettings()canUseFullScreenIntent()openFullScreenIntentSettings()backToForeground()getActiveCalls()checkPermissions()requestPermissions(...)addListener('voipToken', ...)addListener('incomingCall', ...)addListener('callAnswered', ...)addListener('callStarted', ...)addListener('callEnded', ...)addListener('callRejected', ...)addListener('muted', ...)addListener('held', ...)addListener('callStateChanged', ...)addListener('dtmf', ...)addListener('speakerChanged', ...)addListener('audioRouteChanged', ...)addListener('audioSessionActivated', ...)addListener('showIncomingCallUi', ...)addListener('silenceIncomingCall', ...)addListener('incomingCallFailed', ...)addListener('checkReachability', ...)addListener('hasActiveCall', ...)removeAllListeners()- Interfaces
- Type Aliases
- Enums
setup(...)
setup(options: CallSetupOptions) => Promise<void>Configure the plugin. Call once at app start, before any other call method.
Android: registers the PhoneAccount with Telecom and stores the settings so that a push received while the app is killed can still ring.
| Param | Type |
| ------------- | ------------------------------------------------------------- |
| options | CallSetupOptions |
registerVoipToken()
registerVoipToken() => Promise<{ token: string; }>Get the push token used to ring this device.
iOS: PushKit VoIP token. Android: FCM registration token.
Also emitted as the voipToken event (including on token refresh).
Returns: Promise<{ token: string; }>
unregisterVoipToken()
unregisterVoipToken() => Promise<void>Stop receiving call pushes (Android: deletes the FCM token).
displayIncomingCall(...)
displayIncomingCall(options: IncomingCallOptions) => Promise<void>Show the native incoming call UI. Normally triggered natively by a push; call it yourself when the call arrives over your own signalling channel.
| Param | Type |
| ------------- | ------------------------------------------------------------------- |
| options | IncomingCallOptions |
answerCall(...)
answerCall(options: CallIdOptions) => Promise<void>Answer a ringing call from your own UI. Emits callAnswered.
| Param | Type |
| ------------- | ------------------------------------------------------- |
| options | CallIdOptions |
rejectCall(...)
rejectCall(options: CallIdOptions) => Promise<void>Decline a ringing call. Emits callRejected.
| Param | Type |
| ------------- | ------------------------------------------------------- |
| options | CallIdOptions |
startCall(...)
startCall(options: OutgoingCallOptions) => Promise<void>Start an outgoing call. Emits callStarted.
| Param | Type |
| ------------- | ------------------------------------------------------------------- |
| options | OutgoingCallOptions |
setCallActive(...)
setCallActive(options: CallIdOptions) => Promise<void>Mark an outgoing call as connected (remote side picked up).
| Param | Type |
| ------------- | ------------------------------------------------------- |
| options | CallIdOptions |
setCallState(...)
setCallState(options: { callId: string; state: SettableCallState; }) => Promise<void>Report the call's connection state to the system (callkeep's setConnectionState),
e.g. dialing while your signalling connects, active once media flows.
Emits callStateChanged.
| Param | Type |
| ------------- | ------------------------------------------------------------------------------------------- |
| options | { callId: string; state: SettableCallState; } |
endCall(...)
endCall(options: CallIdOptions) => Promise<void>Hang up a call locally. Emits callEnded.
| Param | Type |
| ------------- | ------------------------------------------------------- |
| options | CallIdOptions |
endAllCalls()
endAllCalls() => Promise<void>Hang up every call.
reportEndCall(...)
reportEndCall(options: ReportEndCallOptions) => Promise<void>Report that a call ended for a reason other than the local user hanging up
(remote hang-up, missed, answered elsewhere…). Does not emit callEnded.
| Param | Type |
| ------------- | --------------------------------------------------------------------- |
| options | ReportEndCallOptions |
updateDisplay(...)
updateDisplay(options: UpdateDisplayOptions) => Promise<void>Update the name / handle shown in the system call UI.
| Param | Type |
| ------------- | --------------------------------------------------------------------- |
| options | UpdateDisplayOptions |
setMuted(...)
setMuted(options: { callId: string; muted: boolean; }) => Promise<void>Update the mute state shown in the system UI. Emits muted.
The plugin does not touch your media tracks — mute them in your WebRTC layer.
| Param | Type |
| ------------- | ------------------------------------------------ |
| options | { callId: string; muted: boolean; } |
setSpeaker(...)
setSpeaker(options: { on: boolean; callId?: string; }) => Promise<void>Route audio to the loudspeaker or back to the earpiece. Emits speakerChanged.
| Param | Type |
| ------------- | ---------------------------------------------- |
| options | { on: boolean; callId?: string; } |
setOnHold(...)
setOnHold(options: { callId: string; hold: boolean; }) => Promise<void>Put a call on hold / resume it. Emits held.
| Param | Type |
| ------------- | ----------------------------------------------- |
| options | { callId: string; hold: boolean; } |
sendDTMF(...)
sendDTMF(options: { callId: string; digits: string; }) => Promise<void>Play DTMF digits on a call. Emits dtmf.
| Param | Type |
| ------------- | ------------------------------------------------ |
| options | { callId: string; digits: string; } |
getAudioRoutes()
getAudioRoutes() => Promise<{ routes: AudioRoute[]; }>List available audio routes. Android and iOS.
Returns: Promise<{ routes: AudioRoute[]; }>
setAudioRoute(...)
setAudioRoute(options: { callId: string; route: AudioRouteType; }) => Promise<void>Select an audio route. Android and iOS.
| Param | Type |
| ------------- | ------------------------------------------------------------------------------------- |
| options | { callId: string; route: AudioRouteType; } |
setAvailable(...)
setAvailable(options: { available: boolean; }) => Promise<void>Tell the ConnectionService whether the app can take calls
(e.g. false while logged out) so the user doesn't get stuck in the native UI.
Android only; no-op elsewhere.
| Param | Type |
| ------------- | ------------------------------------ |
| options | { available: boolean; } |
setReachable()
setReachable() => Promise<void>Tell the plugin the JS layer is ready to handle calls. Android only.
Adding a callAnswered listener counts as reachable too. Answered calls are ended if
the app isn't reachable within android.answerReachabilityTimeout.
setCanMakeMultipleCalls(...)
setCanMakeMultipleCalls(options: { allow: boolean; }) => Promise<void>Allow or refuse concurrent calls. When false, a new incoming call while another is in
progress is refused and incomingCallFailed is emitted with error: 'busy', and
startCall rejects. Persisted, so pushes handled while the app is killed respect it.
| Param | Type |
| ------------- | -------------------------------- |
| options | { allow: boolean; } |
getInitialEvents()
getInitialEvents() => Promise<{ events: InitialEvent[]; }>Events that happened before JS attached (e.g. the call was answered from the lock
screen during a cold start), oldest first. They are also delivered to listeners; use
this to route directly at startup. Events with restored: true come from a previous
app process that died before delivering them — their calls no longer exist.
Kept until clearInitialEvents(). Android and iOS (empty on web).
Returns: Promise<{ events: InitialEvent[]; }>
clearInitialEvents()
clearInitialEvents() => Promise<void>Forget the events returned by getInitialEvents().
hasPhoneAccount()
hasPhoneAccount() => Promise<{ value: boolean; }>Whether the calling account is registered and enabled.
Always true in self-managed mode. Android only.
Returns: Promise<{ value: boolean; }>
openPhoneAccountSettings()
openPhoneAccountSettings() => Promise<void>Open the system "Calling accounts" screen (managed mode). Android only.
canUseFullScreenIntent()
canUseFullScreenIntent() => Promise<{ value: boolean; }>Whether the app may show a full-screen incoming call over the lock screen. Android 14+ may revoke this. Android only.
Returns: Promise<{ value: boolean; }>
openFullScreenIntentSettings()
openFullScreenIntentSettings() => Promise<void>Open the settings page to grant full-screen intents (Android 14+).
backToForeground()
backToForeground() => Promise<void>Bring the app to the foreground (e.g. after answering from the lock screen). Android only; rejects as unimplemented on iOS, which doesn't allow it.
getActiveCalls()
getActiveCalls() => Promise<{ calls: ActiveCall[]; }>Currently tracked calls.
Returns: Promise<{ calls: ActiveCall[]; }>
checkPermissions()
checkPermissions() => Promise<CallKitPermissionStatus>Returns: Promise<CallKitPermissionStatus>
requestPermissions(...)
requestPermissions(options?: { permissions: CallKitPermissionType[]; } | undefined) => Promise<CallKitPermissionStatus>| Param | Type |
| ------------- | ------------------------------------------------------ |
| options | { permissions: CallKitPermissionType[]; } |
Returns: Promise<CallKitPermissionStatus>
addListener('voipToken', ...)
addListener(eventName: 'voipToken', listenerFunc: (data: { token: string; }) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | -------------------------------------------------- |
| eventName | 'voipToken' |
| listenerFunc | (data: { token: string; }) => void |
Returns: Promise<PluginListenerHandle>
addListener('incomingCall', ...)
addListener(eventName: 'incomingCall', listenerFunc: (data: IncomingCallEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ---------------------------------------------------------------------------------- |
| eventName | 'incomingCall' |
| listenerFunc | (data: IncomingCallEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('callAnswered', ...)
addListener(eventName: 'callAnswered', listenerFunc: (data: CallAnsweredEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ---------------------------------------------------------------------------------- |
| eventName | 'callAnswered' |
| listenerFunc | (data: CallAnsweredEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('callStarted', ...)
addListener(eventName: 'callStarted', listenerFunc: (data: CallStartedEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | -------------------------------------------------------------------------------- |
| eventName | 'callStarted' |
| listenerFunc | (data: CallStartedEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('callEnded', ...)
addListener(eventName: 'callEnded', listenerFunc: (data: CallEndedEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ---------------------------------------------------------------------------- |
| eventName | 'callEnded' |
| listenerFunc | (data: CallEndedEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('callRejected', ...)
addListener(eventName: 'callRejected', listenerFunc: (data: CallEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ------------------------------------------------------------------ |
| eventName | 'callRejected' |
| listenerFunc | (data: CallEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('muted', ...)
addListener(eventName: 'muted', listenerFunc: (data: MutedEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | -------------------------------------------------------------------- |
| eventName | 'muted' |
| listenerFunc | (data: MutedEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('held', ...)
addListener(eventName: 'held', listenerFunc: (data: HeldEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ------------------------------------------------------------------ |
| eventName | 'held' |
| listenerFunc | (data: HeldEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('callStateChanged', ...)
addListener(eventName: 'callStateChanged', listenerFunc: (data: CallStateChangedEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ------------------------------------------------------------------------------------------ |
| eventName | 'callStateChanged' |
| listenerFunc | (data: CallStateChangedEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('dtmf', ...)
addListener(eventName: 'dtmf', listenerFunc: (data: DtmfEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ------------------------------------------------------------------ |
| eventName | 'dtmf' |
| listenerFunc | (data: DtmfEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('speakerChanged', ...)
addListener(eventName: 'speakerChanged', listenerFunc: (data: SpeakerChangedEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | -------------------------------------------------------------------------------------- |
| eventName | 'speakerChanged' |
| listenerFunc | (data: SpeakerChangedEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('audioRouteChanged', ...)
addListener(eventName: 'audioRouteChanged', listenerFunc: (data: AudioRouteChangedEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | -------------------------------------------------------------------------------------------- |
| eventName | 'audioRouteChanged' |
| listenerFunc | (data: AudioRouteChangedEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('audioSessionActivated', ...)
addListener(eventName: 'audioSessionActivated', listenerFunc: () => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ------------------------------------ |
| eventName | 'audioSessionActivated' |
| listenerFunc | () => void |
Returns: Promise<PluginListenerHandle>
addListener('showIncomingCallUi', ...)
addListener(eventName: 'showIncomingCallUi', listenerFunc: (data: IncomingCallEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ---------------------------------------------------------------------------------- |
| eventName | 'showIncomingCallUi' |
| listenerFunc | (data: IncomingCallEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('silenceIncomingCall', ...)
addListener(eventName: 'silenceIncomingCall', listenerFunc: (data: CallEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ------------------------------------------------------------------ |
| eventName | 'silenceIncomingCall' |
| listenerFunc | (data: CallEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('incomingCallFailed', ...)
addListener(eventName: 'incomingCallFailed', listenerFunc: (data: CallFailedEvent) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ------------------------------------------------------------------------------ |
| eventName | 'incomingCallFailed' |
| listenerFunc | (data: CallFailedEvent) => void |
Returns: Promise<PluginListenerHandle>
addListener('checkReachability', ...)
addListener(eventName: 'checkReachability', listenerFunc: () => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | -------------------------------- |
| eventName | 'checkReachability' |
| listenerFunc | () => void |
Returns: Promise<PluginListenerHandle>
addListener('hasActiveCall', ...)
addListener(eventName: 'hasActiveCall', listenerFunc: () => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ---------------------------- |
| eventName | 'hasActiveCall' |
| listenerFunc | () => void |
Returns: Promise<PluginListenerHandle>
removeAllListeners()
removeAllListeners() => Promise<void>Interfaces
CallSetupOptions
| Prop | Type | Description |
| ------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| appName | string | Name of the calling account on Android. Defaults to the app label. iOS always shows the app's display name (CallKit no longer accepts a custom one). |
| supportsVideo | boolean | Default: false |
| imageName | string | Android drawable / iOS asset name used as the calling account / CallKit icon. |
| android | AndroidSetupOptions | |
| ios | IOSSetupOptions | |
AndroidSetupOptions
| Prop | Type | Description |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| selfManaged | boolean | Self-managed ConnectionService: the app owns the call UI and no "enable calling account" step is needed. Default: true. Set to false for managed mode (system dialer UI, needs phone permissions). |
| showIncomingCallNotification | boolean | Show the plugin's native incoming-call notification (full-screen intent + Answer/Decline) in self-managed mode. Default: true. |
| incomingCallScreen | 'native' | 'app' | What the full-screen incoming-call alert opens over the lock screen: - native (default): the plugin's built-in call screen (caller + Answer / Decline). Shows instantly and reliably over a secure lock screen; Answer then opens your app. - app: your app's own call page (listen to incomingCall). Needs the WebView to load first. Restyle / translate the native screen by overriding the ionic_callkit_* resources. |
| ringtoneSound | string | Ringtone file in android/app/src/main/res/raw/ (name with or without extension). Default: the device ringtone. The plugin rings itself; silent / vibrate modes are respected. |
| vibrate | boolean | Vibrate while ringing (unless the phone is on silent). Default: true. |
| notificationIcon | string | Small icon (drawable or mipmap resource name) for call notifications. |
| incomingCallChannelName | string | Channel name for incoming calls. Default: "Incoming calls". |
| foregroundService | false | { channelId?: string; channelName?: string; notificationTitle?: string; notificationIcon?: string; } | Ongoing-call foreground service notification (keeps the mic alive in the background). Enabled by default; pass false to disable. |
| canMakeMultipleCalls | boolean | Allow more than one simultaneous call. Default: true. See setCanMakeMultipleCalls. |
| answerReachabilityTimeout | number | End an answered call if the app hasn't called setReachable() (or added a callAnswered listener) within this many milliseconds — e.g. the WebView failed to start after answering from the lock screen. Default: 10000. 0 disables. |
| displayCallReachabilityTimeout | number | If set, an incoming call is dropped when the JS layer hasn't called setReachable() within this many milliseconds. |
IOSSetupOptions
| Prop | Type | Description |
| ------------------------------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ringtoneSound | string | Ringtone sound file in the app bundle. |
| maximumCallGroups | number | Default: 1 |
| maximumCallsPerCallGroup | number | Default: 1 |
| includesCallsInRecents | boolean | Default: false |
| handleType | 'number' | 'generic' | 'email' | CallKit handle type for handle values. Default: generic. |
| audioSession | { autoConfigure?: boolean; categoryOptions?: number; mode?: string; } | Audio session CallKit activates for the call (callkeep's audioSession setting). Set autoConfigure: false if your media SDK configures AVAudioSession itself. |
IncomingCallOptions
| Prop | Type | Description |
| ---------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| callId | string | |
| callerName | string | |
| handle | string | Phone number / user id shown as the caller address. Defaults to callerName. |
| hasVideo | boolean | |
| payload | Record<string, string> | Extra data passed back in the incomingCall event. |
CallIdOptions
| Prop | Type |
| ------------ | ------------------- |
| callId | string |
OutgoingCallOptions
| Prop | Type | Description |
| ---------------- | -------------------- | --------------------------------------------------------------- |
| callId | string | |
| calleeName | string | |
| handle | string | Phone number / user id of the callee. Defaults to calleeName. |
| hasVideo | boolean | |
ReportEndCallOptions
| Prop | Type |
| ------------ | ------------------------------------------------------- |
| callId | string |
| reason | CallEndReason |
UpdateDisplayOptions
| Prop | Type |
| ---------------- | ------------------- |
| callId | string |
| callerName | string |
| handle | string |
AudioRoute
| Prop | Type |
| -------------- | --------------------------------------------------------- |
| name | string |
| type | AudioRouteType |
| selected | boolean |
InitialEvent
| Prop | Type | Description |
| --------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| name | string | Event name, e.g. incomingCall, callAnswered. |
| data | Record<string, unknown> | |
| timestamp | number | Epoch milliseconds. |
| restored | boolean | From a previous app process that died before delivering it; the call is gone. |
ActiveCall
| Prop | Type |
| ---------------- | ----------------------------------------------- |
| callId | string |
| callerName | string |
| handle | string |
| state | CallState |
CallKitPermissionStatus
| Prop | Type |
| ------------------- | ----------------------------------------------------------- |
| microphone | PermissionState |
| notifications | PermissionState |
| phone | PermissionState |
PluginListenerHandle
| Prop | Type |
| ------------ | ----------------------------------------- |
| remove | () => Promise<void> |
IncomingCallEvent
| Prop | Type |
| ---------------- | --------------------------------------------------------------- |
| callerName | string |
| handle | string |
| hasVideo | boolean |
| payload | Record<string, string> |
CallAnsweredEvent
| Prop | Type |
| -------------- | -------------------- |
| hasVideo | boolean |
CallStartedEvent
| Prop | Type |
| ---------------- | ------------------- |
| calleeName | string |
| handle | string |
CallEndedEvent
| Prop | Type | Description |
| ------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| reason | CallEndReason | Set when the call was ended remotely by a call_ended push (Missed if it was never answered, otherwise RemoteEnded). Absent when the user hung up. |
CallEvent
| Prop | Type |
| ------------ | ------------------- |
| callId | string |
MutedEvent
| Prop | Type |
| ----------- | -------------------- |
| muted | boolean |
HeldEvent
| Prop | Type |
| ---------- | -------------------- |
| hold | boolean |
CallStateChangedEvent
| Prop | Type |
| ----------- | ----------------------------------------------- |
| state | CallState |
DtmfEvent
| Prop | Type |
| ------------ | ------------------- |
| digits | string |
SpeakerChangedEvent
| Prop | Type |
| ------------ | -------------------- |
| callId | string |
| on | boolean |
AudioRouteChangedEvent
| Prop | Type | Description |
| ------------ | ------------------- | ----------------------------------------------------------------------- |
| callId | string | |
| output | string | Android: EARPIECE | SPEAKER | WIRED_HEADSET | BLUETOOTH | STREAMING |
CallFailedEvent
| Prop | Type |
| ---------------- | ------------------- |
| callerName | string |
| handle | string |
| error | string |
Type Aliases
Record
Construct a type with a set of properties K of type T
{ [P in K]: T; }
SettableCallState
States an app may set with setCallState.
'initializing' | 'ringing' | 'dialing' | 'active' | 'held'
AudioRouteType
'Phone' | 'Speaker' | 'Headset' | 'Bluetooth'
CallState
Connection states reported by callStateChanged / getActiveCalls.
'initializing' | 'new' | 'ringing' | 'dialing' | 'active' | 'held' | 'disconnected' | 'pulling'
PermissionState
'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'
CallKitPermissionType
microphone: RECORD_AUDIOnotifications: POST_NOTIFICATIONS (Android 13+)phone: READ_PHONE_STATE / CALL_PHONE — managed mode only; must be declared in your app manifest
'microphone' | 'notifications' | 'phone'
Enums
CallEndReason
| Members | Value |
| ----------------------- | -------------- |
| Failed | 1 |
| RemoteEnded | 2 |
| Unanswered | 3 |
| AnsweredElsewhere | 4 |
| DeclinedElsewhere | 5 |
| Missed | 6 |
