Expo-callkit-telecom released: easily integrate CallKit/Core-Telecom with LiveKit on React Native

I’ve been building a voice/video calling app with the LiveKit RN SDK, and I pulled the native call UI piece out into a standalone Expo module: expo-callkit-telecom. I found CallKit integration one of the harder parts of building a VoIP app so I hope this makes it easier for others.

What’s in it:

  • CallKit on iOS and Jetpack androidx.core:core-telecom on Android behind one typed CallSession API
  • Expo config plugin (entitlements, background modes, mic permission, ringtone bundling, FCM service registration)
  • Native VoIP push parsing on both APNs VoIP and FCM data messages

Docs: https://mfairley.github.io/expo-callkit-telecom/
Repo: https://github.com/mfairley/expo-callkit-telecom

Comparison with react-native-callkeep

react-native-callkeep is the long-standing React Native library in this space. expo-callkit-telecom solves the same problem but is built on the current generation of platform APIs: Jetpack androidx.core:core-telecom on Android, Swift + Kotlin, the Expo Modules API with a config plugin, and RTCAudioSession coordination for manual-audio WebRTC stacks like LiveKit. It also parses APNs VoIP and FCM data payloads natively, so the cold-start incoming-call case works without app-side glue.


@michael, Two questions worth surfacing for RN voice-agent readers landing on this:

  • Audio session ownership: does the CallSession API expect the app to call LiveKit’s manual audio start/stop, or does the module own the lifecycle end-to-end (CallKit activates, module unlocks RTCAudioSession, LiveKit publishes)? Recurring failure mode on RN + LiveKit voice apps.

  • Android coverage: on older Android versions where androidx.core:core-telecom delegates to legacy ConnectionService, is the fallback handled inside the module or do callers need to gate by API level?

Bookmarking for the next RN VoIP thread that lands.

  • Audio session ownership: does the CallSession API expect the app to call LiveKit’s manual audio start/stop, or does the module own the lifecycle end-to-end (CallKit activates, module unlocks RTCAudioSession, LiveKit publishes)? Recurring failure mode on RN + LiveKit voice apps.

The module handles audio session activation for you, so you would not use LiveKit to start/stop the audio session.

  • Android coverage: on older Android versions where androidx.core:core-telecom delegates to legacy ConnectionService, is the fallback handled inside the module or do callers need to gate by API level?

It should work fine as long as you use sdk > 26 but I haven’t thoroughly tested it.

Thanks for sharing :lk-party: this looks really useful