Library / SDK
react-native-webrtc/react-native-callkeep avatar
react-native-webrtc/react-native-callkeep

react-native-callkeep: putting VoIP calls into the system UI on iOS and Android

iOS CallKit framework and Android ConnectionService for React Native

1,061 stars512 forksJavaISC

At a glance

What is it?
React Native CallKeep wraps the iOS CallKit framework and the Android ConnectionService so a React Native VoIP app can present calls in the system's own interface, with lock screen controls, incoming call screens, and the platform's own call log. It requires a real device, since neither framework works on a simulator.
Who is it for?
react-native-callkeep fits a React Native team shipping a VoIP app that needs its calls to appear in the system call UI, with lock screen actions and the platform's own call log entries, and that owns the signalling and media stack itself. It is the wrong choice if you want a WebRTC stack, since the media and signalling are not part of this library.
Can I use it commercially?
Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

System call UI, not a call screen you drew yourself

React Native CallKeep utilises a brand new iOS 10 framework, CallKit, and Android ConnectionService to make the life easier for VoIP developers using React Native.

The problem it solves is the one every VoIP app on a phone eventually hits. A user expects a call to look like a call, with an incoming call screen, lock screen controls, a mute and speaker button, and an entry in the system call log. If your app draws its own in-app call screen, the user gets something that feels like a video game, and the phone's own call UI does not know the call exists, so the lock screen shows nothing and the call log is empty.

CallKit and ConnectionService are the platform answers to that. On iOS, CallKit is the framework that makes your VoIP calls appear as real calls in the system interface, with the system's own incoming call screen, the lock screen actions, and the recents list. On Android, ConnectionService is the equivalent, letting your app register as a phone account and have calls appear in the dialer and status bar.

By wrapping both, the library gives a React Native app the platform behaviour with a JavaScript API. That is the whole value proposition, and it is a genuinely hard thing to do yourself, because the two platforms have quite different models and the React Native bridge is not a natural place to express them.

The limitation is stated immediately and prominently. CallKit and ConnectionService are only available on real devices, and this library will not work on simulators. That is a property of the frameworks rather than of the wrapper, and it means the development loop for anything touching this code is physical hardware on both platforms.

The project links the official CallKit framework documentation, an introduction to CallKit, the Android ConnectionService reference, and Google's guide on building a calling app, for anyone who needs the platform-level detail rather than the wrapper's API.

Setup is one call, and the options differ sharply per platform

The setup pattern is a single options object passed to `RNCallKeep.setup(options)`, which returns a promise resolving to whether the setup was accepted.

js
import RNCallKeep from 'react-native-callkeep';

const options = {
  ios: {
    appName: 'My app name',
  },
  android: {
    alertTitle: 'Permissions required',
    alertDescription: 'This application needs to access your phone accounts',
    cancelButton: 'Cancel',
    okButton: 'ok',
    imageName: 'phone_account_icon',
    additionalPermissions: [PermissionsAndroid.PERMISSIONS.example],
    // Required to get audio in background when using Android 11
    foregroundService: {
      channelId: 'com.company.my',
      channelName: 'Foreground service for my app',
      notificationTitle: 'My app is running on background',
      notificationIcon: 'Path to the resource icon of the notification',
    }, 
  }
};

RNCallKeep.setup(options).then(accepted => {});

The two platforms take entirely different options, and that is not an accident of the API design. The iOS side is mostly about how the system should present your app, and the Android side is mostly about the permission flow, because on Android the user has to grant your app access to phone accounts before any of this works.

On iOS, `appName` is required and is displayed on the system UI when incoming calls are received. `imageName` is optional and is shown on the system UI during the call. `ringtoneSound` is optional and is played when an incoming call is received, with the system using the default ringtone if it is not provided. `handleType` tells iOS what kinds of handle your app can handle, and it accepts `generic`, `number` as the default, and `email`.

There are also the call-group settings, `maximumCallGroups` defaulting to 3 and `maximumCallsPerCallGroup` defaulting to 1, which is the no-conferencing default. `supportsVideo` defaults to true. `includesCallsInRecents` defaults to true and applies from iOS 11 onwards. `displayCallReachabilityTimeout` is a timeout in milliseconds that starts a check of whether the app is reachable and ends the call if it is not, and you have to call `setReachable()` as soon as your JavaScript application is started, which is the part that catches people out.

The audio session is configurable too, with `categoryOptions` and `mode` overriding the default AVAudioSession setCategory options and setMode mode.

On iOS you can set up before the bridge exists

There is an iOS-only alternative to the JavaScript setup, and it exists for a specific reason.

Alternative on iOS you can perform setup in `AppDelegate.m`. Doing this allows capturing events prior to the react native event bridge being up. Please be aware that calling setup in `AppDelegate.m` will ignore any subsequent calls to `RNCallKeep.setup()`.

The example shows what that looks like:

objective-c
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ 
  self.bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

  [RNCallKeep setup:@{
    @"appName": @"Awesome App",
    @"maximumCallGroups": @3,
    @"maximumCallsPerCallGroup": @1,
    @"supportsVideo": @NO,
  }];

  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:self.bridge
                                                   moduleName:@"App"
                                            initialProperties:nil];

  // ======== OTHER CODE REDACTED ==========

  return YES;
}

The reason to set up in the app delegate is the cold start race. If your app is launched by an incoming call notification, the JavaScript bundle may not have finished loading when the call arrives. A setup call from JavaScript happens too late to register the call with the system in time, and the incoming call is missed. Doing it natively in the app delegate registers before the bridge is even created, which is the only way to catch that first call.

The cost is stated plainly: calling setup in `AppDelegate.m` will ignore any subsequent calls to `RNCallKeep.setup()`. So the two paths are mutually exclusive, and a project that needs the cold start behaviour has to do the configuration in one place, natively, and not also in JavaScript.

The Objective-C dictionary keys mirror the JavaScript option names, with `appName`, `maximumCallGroups`, `maximumCallsPerCallGroup`, and `supportsVideo` all spelled the same way, so the translation between the two call sites is mechanical.

Android needs an account, a permission prompt, and a foreground service

The Android options are mostly about getting permission, because the platform requires the user to grant your app access to phone accounts before a ConnectionService can do anything useful.

`alertTitle` is required and is the title for the alert used to ask the user for the phone account permission, and `alertDescription` is required and is the description for the same alert. `cancelButton` and `okButton` are also required and are the labels for the two buttons on that prompt.

`imageName` is optional and is the image to use in the Android phone application's native UI for enabling and disabling calling accounts, with a very specific constraint: it should be a 48x48 HDPI grayscale PNG image, must be in the drawable resources for the parent application, and must use only lowercase and underscore characters, since Java does not like capital letters on resources.

That last constraint is the sort of thing that costs an afternoon if you do not read it. The resource naming rule is a Java and Android requirement rather than anything about this library, but the library is where you would look for it.

`additionalPermissions` is optional and takes an array of the PermissionsAndroid style entries, and the documented reason to use it is to simplify permission flows and avoid multiple popups to the user at different times. You are asking for everything you need in one prompt rather than discovering the second requirement later.

`selfManaged` is the advanced option. When set to true, CallKeep will configure itself to run as a self managed connection service, and the documentation says it is an advanced topic and points to Google's own documentation on the matter.

And then the `foregroundService` block, with the comment that it is required to get audio in background when using Android 11. It takes a `channelId`, a `channelName`, a `notificationTitle`, and a `notificationIcon`. The channel identifier in the example is a package-style string, `com.company.my`, which is what Android 11 requires for a foreground service channel, and the notification icon is a path to the resource icon.

This is the part that catches teams out on upgrade. Android 11 changed how background audio works, and a VoIP app that worked on Android 10 can lose its background audio without this configuration.

PushKit, Android 11, and the shape of the repository

The summary has separate sections for PushKit and for Android 11, which tells you those are the two areas where a reader is most likely to be stuck.

PushKit is the iOS mechanism for reporting an incoming call that was initiated elsewhere, over push, so the device can display the incoming call screen even if the app was not running. For a React Native VoIP app on iOS, that is not an optional detail, because without it a call pushed to a terminated app cannot be surfaced as a system call. The summary links the relevant material, and the implementation of that flow is the kind of thing where the platform documentation and the library's own behaviour have to line up.

The Android 11 section is where the foreground service configuration described above gets its context, since Android 11 restricted how an app can keep running in the background and keep the microphone open.

The repository layout is conventional for a React Native library with native code. There is `ios/` and `android/` for the native sides, an `index.js` at the root as the JavaScript entry, and an `index.d.ts` for the TypeScript definitions, which the package.json points at with the `typings` field. There is an `RNCallKeep.podspec` for the CocoaPods integration, a `docs/` directory with the iOS and Android installation guides and the pictures used in the summary, an `example/` directory with a full React Native app including its own `android/` and `ios/` folders, and a `tests/` directory.

Two migration documents sit at the root, `MIGRATION_v2_v3.md` and `MIGRATION_v3_v4.md`, which is the clearest sign of the project's history. The tags show the same: 4.0.0 in November 2020, 4.0.1 in December 2020, and 4.1.0 in March 2021, while the package version in the metadata is 4.3.17. So the published npm version is well past the last tag, and the migration guides cover the two major version jumps that are behind it.

There is a `CONTRIBUTING` section and an `AUTHORS` file, and the package metadata points the author, maintainers, and contributors fields all at the AUTHORS file, which is a slightly old-fashioned convention that keeps the attribution in one place.

The peer dependency is `react-native` at 0.40.0 or newer, and the runtime dependencies list is empty, so the library adds nothing to your JavaScript bundle beyond its own code.

Where react-native-callkeep is the wrong tool

The boundaries are clear, and the main one is easy to miss because the name does not advertise it.

First, this is not a WebRTC stack. The package is in the react-native-webrtc organisation, and the topics include webrtc and voip, but the library provides no media handling and no signalling. It is the system UI integration for calls that something else is already making. A team looking for peer connections, ICE, codecs, or a signalling server needs react-native-webrtc itself, and needs this library alongside it. Reading the package description, iOS 10 CallKit and Android ConnectionService Framework For React Native, the scope is the framework, not the call.

Second, it does not work on simulators, on either platform. That is not a limitation of the wrapper, it is a property of CallKit and ConnectionService, and the project states it without hedging. For a team whose testing story is an emulator, this means real devices are part of the cost of the feature, and a testing matrix that has to cover both platforms physically.

Third, the API is thin, which cuts both ways. There is no abstraction over the two platforms beyond the fact that both do the same job. Every platform-specific behaviour, the call groups on iOS, the self managed connection service on Android, the foreground service, the PushKit flow, is exposed as a separate option, and there is no portable model that means the same thing on both. A cross-platform app has to write two configurations and keep them in step, and the two do not have the same capabilities.

Fourth, the version situation is worth checking. The last tag is 4.1.0 from 2021, the package version is 4.3.17, and the last push was on 2026-09-14. So the code is being touched while the published versions have not been tagged in years, which for a library that has to be pinned in an app's dependency list is a supply-chain consideration. The migration documents show the interface has changed across major versions before, and there is no tag to pin against to see exactly which change landed where.

Fifth, and this is the operational one, the whole feature depends on platform permissions and platform versions behaving as documented. The phone account permission on Android, the foreground service on Android 11, the reachability timeout on iOS with its requirement that you call `setReachable()` early, the system UI requirements on both. Each of these is a place where a change in a future OS release can break a call flow that worked, and the library cannot insulate an app from that.

Editorial conclusion

react-native-callkeep fits a React Native team shipping a VoIP app that needs its calls to appear in the system call UI, with lock screen actions and the platform's own call log entries, and that owns the signalling and media stack itself. It is the wrong choice if you want a WebRTC stack, since the media and signalling are not part of this library. Check three things before you adopt it: that you have real devices on both platforms, since the project states the library will not work on simulators, that your app can supply a foreground service configuration on Android 11 for background audio, and that the peer dependency on react-native at 0.40.0 or newer is compatible with the React Native version your app actually uses.

Frequently asked questions

What does react-native-callkeep do and what does it not do?

It wraps the iOS CallKit framework and the Android ConnectionService so a React Native VoIP app can present calls in the system interface, with incoming call screens, lock screen controls, and call log entries. It provides no media handling and no signalling; it is the system UI integration for calls something else is making.

Can I test react-native-callkeep on a simulator?

No. The project states that CallKit and ConnectionService are only available on real devices, and that this library will not work on simulators. Any testing of this feature needs physical hardware on both platforms.

How do I set up react-native-callkeep?

Pass an options object to `RNCallKeep.setup(options)`, which returns a promise. The iOS side needs at least an `appName`, and the Android side needs `alertTitle`, `alertDescription`, `cancelButton`, and `okButton` for the phone account permission prompt. On iOS you can alternatively call setup in `AppDelegate.m` to catch events before the React Native bridge is up, and doing so ignores later calls to `RNCallKeep.setup()`.

What does the foregroundService option on Android do?

It is required to get audio in background when using Android 11, and it takes a channelId, a channelName, a notificationTitle, and a notificationIcon. Without it, a VoIP app that worked on earlier Android versions can lose its background audio.

Official sources

  1. Issues
  2. License: ISC
  3. react-native-webrtc/react-native-callkeep on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/react-native-webrtc-react-native-callkeep.svg)](https://hysenlabs.com/projects/react-native-webrtc-react-native-callkeep)

Was this useful?