yarn add @react-native-firebase/app \
@react-native-firebase/messaging \
@notifee/react-native \
react-native-voip-push-notification \
react-native-callkeep
npx pod-installReact Native
If you are on version 1.2 or below, you would need to upgrade to 1.3 or above to follow the setup. As 1.3 release had breaking changes with respect to setting up of push notifications. We recommend to update to the current latest version.
Add push notifications for ringing calls to your React Native project. Covers both Android and iOS setup.
Users receive push notifications for incoming calls and can accept or reject directly from the notification.
| Android preview | iOS preview |
|---|---|
![]() | ![]() |
Full-screen notifications are displayed when the phone screen is locked or the app is active (foreground state). However, when the app is terminated or in the background and the screen is awake, notifications may appear as heads-up notifications instead of a full-screen alerts.
Add push provider credentials to Stream
Follow these guides to add push providers:
- Android - Firebase Cloud Messaging
- iOS - Apple Push Notification Service (APNs)
Install Dependencies
Package purposes:
- @react-native-firebase/app, @react-native-firebase/messaging - Handle Firebase Cloud Messaging on Android
- @notifee/react-native - Customize and display push notifications
- react-native-voip-push-notification - Handle PushKit notifications on iOS
- react-native-callkeep - Report calls to iOS CallKit
iOS-specific setup
Disable Firebase integration
Firebase Cloud Messaging is not used for iOS. Disable auto-linking unless Firebase is needed for other purposes. Create react-native.config.js in your project root:
module.exports = {
dependencies: {
"@react-native-firebase/app": {
platforms: {
ios: null,
},
},
"@react-native-firebase/messaging": {
platforms: {
ios: null,
},
},
},
};Then run pod install again:
npx pod-installAdd background modes
In Xcode, open Info.plist and add UIBackgroundModes:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>processing</string>
<string>remote-notification</string>
<string>voip</string>
</array>Enable push notifications
Enable Push Notifications capability in Xcode: Project > Signing & Capabilities.
Update AppDelegate
Update AppDelegate.m, AppDelegate.mm, or AppDelegate.swift with the following.
Add headers
Import required headers:
#import <WebRTC/RTCAudioSession.h>
#import "RNCallKeep.h"
#import <PushKit/PushKit.h>
#import "RNVoipPushNotificationManager.h"
#import "StreamVideoReactNative.h"import RNCallKeep
import PushKit
import WebRTC
import RNVoipPushNotificationAdditionally, to import an Objective-C method you need to add the following import to your bridging header file:
#import "StreamVideoReactNative.h"If you dont have a bridging header file, you need to create one, see Apple's documentation on Objective-C and Swift interoperability on how to create one through xcode.
Initialize on app launch
Set up CallKeep and register VoIP in didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *localizedAppName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"];
NSString *appName = [[[NSBundle mainBundle] infoDictionary]objectForKey :@"CFBundleDisplayName"];
[RNCallKeep setup:@{
@"appName": localizedAppName != nil ? localizedAppName : appName,
@"supportsVideo": @YES,
// pass @YES here if you want the call to be shown in calls history in the built-in dialer app
@"includesCallsInRecents": @NO,
}];
[RNVoipPushNotificationManager voipRegistration];
// the rest
}override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let localizedAppName = Bundle.main.localizedInfoDictionary?["CFBundleDisplayName"] as? String
let appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as! String
RNCallKeep.setup([
"appName": localizedAppName ?? appName,
"supportsVideo": true,
// pass true here if you want the call to be shown in calls history in the built-in dialer app
"includesCallsInRecents": false,
])
RNVoipPushNotificationManager.voipRegistration()
// the rest
}Add Audio Session methods
Handle WebRTC audio session activation/deactivation from CXProvider delegate:
- (void) provider:(CXProvider *) provider didActivateAudioSession:(AVAudioSession *) audioSession {
[[RTCAudioSession sharedInstance] audioSessionDidActivate:[AVAudioSession sharedInstance]];
}
- (void) provider:(CXProvider *) provider didDeactivateAudioSession:(AVAudioSession *) audioSession {
[[RTCAudioSession sharedInstance] audioSessionDidDeactivate:[AVAudioSession sharedInstance]];
}func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidActivate(AVAudioSession.sharedInstance())
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(AVAudioSession.sharedInstance())
}Add PushKit methods
Process VoIP tokens and send to react-native-voip-push-notification:
// handle updated push credentials
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
[RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];
}// handle updated push credentials
func pushRegistry(
_ registry: PKPushRegistry,
didUpdate credentials: PKPushCredentials,
for type: PKPushType
) {
RNVoipPushNotificationManager.didUpdate(credentials, forType: type.rawValue)
}Handle incoming VoIP push notifications and display the incoming call notification:
// handle incoming pushes
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
// process the payload and store it in the native module's cache
NSDictionary *stream = payload.dictionaryPayload[@"stream"];
NSString *uuid = [[NSUUID UUID] UUIDString];
NSString *createdCallerName = stream[@"created_by_display_name"];
NSString *cid = stream[@"call_cid"];
NSString *videoIncluded = stream[@"video"];
BOOL hasVideo = [videoIncluded isEqualToString:@"false"] ? NO : YES;
// store the call cid and uuid in the native module's cache
[StreamVideoReactNative registerIncomingCall:cid uuid:uuid];
// set the completion handler - this one is called by the JS SDK
[RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion];
// send event to JS - the JS SDK will handle the rest and call the 'completionHandler'
[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
// display the incoming call notification
[RNCallKeep reportNewIncomingCall: uuid
handle: createdCallerName
handleType: @"generic"
hasVideo: hasVideo
localizedCallerName: createdCallerName
supportsHolding: NO
supportsDTMF: NO
supportsGrouping: NO
supportsUngrouping: NO
fromPushKit: YES
payload: stream
withCompletionHandler: nil];
}// handle incoming pushes
func pushRegistry(
_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: @escaping () -> Void
) {
// process the payload
guard
let stream = payload.dictionaryPayload["stream"] as? [String: Any],
let createdCallerName = stream["created_by_display_name"] as? String,
let cid = stream["call_cid"] as? String
else {
completion()
return
}
// Check if user is busy BEFORE registering the call
let shouldReject = StreamVideoReactNative.shouldRejectCallWhenBusy()
let hasAnyActiveCall = StreamVideoReactNative.hasAnyActiveCall()
if shouldReject && hasAnyActiveCall {
// Complete the VoIP notification without showing CallKit UI
completion()
return
}
let uuid = UUID().uuidString
let videoIncluded = stream["video"] as? String
let hasVideo = videoIncluded == "false" ? false : true
StreamVideoReactNative.registerIncomingCall(cid, uuid: uuid)
RNVoipPushNotificationManager.addCompletionHandler(uuid, completionHandler: completion)
RNVoipPushNotificationManager.didReceiveIncomingPush(
with: payload,
forType: type.rawValue
)
// display the incoming call notification
RNCallKeep.reportNewIncomingCall(
uuid,
handle: createdCallerName,
handleType: "generic",
hasVideo: hasVideo,
localizedCallerName: createdCallerName,
supportsHolding: false,
supportsDTMF: false,
supportsGrouping: false,
supportsUngrouping: false,
fromPushKit: true,
payload: stream,
withCompletionHandler: nil
)
}Android-specific setup
- Create a Firebase project at Firebase console
- Add your Android app in Project settings > Your apps. Use the same package name as
android.packagein app.json - Download google-services.json to
/android/app/google-services.json - Enable the
google-servicesplugin:
buildscript {
dependencies {
// ... other dependencies
classpath 'com.google.gms:google-services:4.3.15'
}
}apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'The google-services.json file contains unique and non-secret identifiers of your Firebase project. For more information, see Understand Firebase Projects.
Update AndroidManifest.xml
Add permissions and foreground service:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- We declare the permissions needed for using foreground service to keep call alive -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<service
android:name="app.notifee.core.ForegroundService"
tools:replace="android:foregroundServiceType"
android:stopWithTask="true"
android:foregroundServiceType="shortService|camera|microphone|mediaPlayback" />
/>The foreground service and its permissions are essential for displaying incoming call notifications when the app is running in the background, as well as for maintaining video and audio calls during background operation. When uploading the app to the Play Store, it is necessary to declare the foreground service permissions in the Play Console and provide an explanation of their usage. This includes adding a link to a video that demonstrates how the foreground service is utilized for video and audio calls. This procedure is required only once. For more details, refer to here.
The permissions to be explained are:
android.permission.FOREGROUND_SERVICE_CAMERA- To access camera when app goes to backgroundandroid.permission.FOREGROUND_SERVICE_MICROPHONE- To access microphone when app goes to backgroundandroid.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK- To play video and audio tracks when the app goes to background
Add Firebase message handlers
Add SDK utility functions to Firebase notification listeners:
import messaging from "@react-native-firebase/messaging";
import notifee from "@notifee/react-native";
import {
isFirebaseStreamVideoMessage,
firebaseDataHandler,
onAndroidNotifeeEvent,
isNotifeeStreamVideoEvent,
} from "@stream-io/video-react-native-sdk";
export const setFirebaseListeners = () => {
// Set up the background message handler
messaging().setBackgroundMessageHandler(async (msg) => {
if (isFirebaseStreamVideoMessage(msg)) {
await firebaseDataHandler(msg.data);
} else {
// your other background notifications (if any)
}
});
// on press handlers of background notifications
notifee.onBackgroundEvent(async (event) => {
if (isNotifeeStreamVideoEvent(event)) {
await onAndroidNotifeeEvent({ event, isBackground: true });
} else {
// your other background notifications (if any)
}
});
// Optionally: set up the foreground message handler
messaging().onMessage((msg) => {
if (isFirebaseStreamVideoMessage(msg)) {
firebaseDataHandler(msg.data);
} else {
// your other foreground notifications (if any)
}
});
// Optionally: on press handlers of foreground notifications
notifee.onForegroundEvent((event) => {
if (isNotifeeStreamVideoEvent(event)) {
onAndroidNotifeeEvent({ event, isBackground: false });
} else {
// your other foreground notifications (if any)
}
});
};Firebase message handlers:
- onMessage - Skip if foreground notifications not needed (incoming call screen shows automatically)
- isFirebaseStreamVideoMessage - Checks if push message is video-related
- firebaseDataHandler - Processes message and displays notification via
@notifee/react-native
Notifee event handlers:
- onForegroundEvent - Skip if foreground notifications not added
- isNotifeeStreamVideoEvent - Checks if event is video-related
- onAndroidNotifeeEvent - Processes accept/decline actions
If you had disabled the installation of Firebase on iOS, add the above method only for Android using the Platform-specific extensions for React Native.
For example, say you add the following files in your project:
setFirebaseListeners.android.ts
setFirebaseListeners.tsThe method above must only be added to the file that .android extension. The other file must add the method but do nothing like below:
export const setFirebaseListeners = () => {
// do nothing
};This is to ensure that @react-native-firebase/messaging is only imported on the Android platform.
Request notification permissions
Request permissions using react-native-permissions:
import { requestNotifications } from "react-native-permissions";
// This will request POST_NOTIFICATION runtime permission for Anroid 13+
await requestNotifications(["alert", "sound"]);For a comprehensive guide on requesting all required permissions (camera, microphone, bluetooth, and notifications), see Manage Native Permissions.
Setup the push notifications configuration for the SDK
Configure push via StreamVideoRN.setPushConfig. Override notification texts, set push provider names, and customize ringtone:
import {
StreamVideoClient,
StreamVideoRN,
User,
} from "@stream-io/video-react-native-sdk";
import { AndroidImportance } from "@notifee/react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { STREAM_API_KEY } from "../../constants";
export function setPushConfig() {
StreamVideoRN.setPushConfig({
ios: {
// add your push_provider_name for iOS that you have setup in Stream dashboard
pushProviderName: __DEV__ ? "apn-video-staging" : "apn-video-production",
},
android: {
// the name of android notification icon (Optional, defaults to 'ic_launcher')
smallIcon: "ic_notification",
// add your push_provider_name for Android that you have setup in Stream dashboard
pushProviderName: __DEV__
? "firebase-video-staging"
: "firebase-video-production",
// configure the notification channel to be used for incoming calls for Android.
incomingCallChannel: {
id: "stream_incoming_call",
name: "Incoming call notifications",
// This is the advised importance of receiving incoming call notifications.
// This will ensure that the notification will appear on-top-of applications.
importance: AndroidImportance.HIGH,
// optional: if you dont pass a sound, default ringtone will be used
sound: "<url to the ringtone>",
},
// configure the functions to create the texts shown in the notification
// for incoming calls in Android.
incomingCallNotificationTextGetters: {
getTitle: (userName: string) => `Incoming call from ${userName}`,
getBody: (_userName: string) => "Tap to answer the call",
getAcceptButtonTitle: () => "Accept",
getDeclineButtonTitle: () => "Decline",
},
},
// add the async callback to create a video client
// for incoming calls in the background on a push notification
createStreamVideoClient: async () => {
const userId = await AsyncStorage.getItem("@userId");
const userName = await AsyncStorage.getItem("@userName");
if (!userId) return undefined;
// an example promise to fetch token from your server
const tokenProvider = async (): Promise<string> =>
yourServerAPI.getTokenForUser(userId).then((auth) => auth.token);
const user: User = { id: userId, name: userName };
return StreamVideoClient.getOrCreateInstance({
apiKey: STREAM_API_KEY, // pass your stream api key
user,
tokenProvider,
});
},
});
}Always use StreamVideoClient.getOrCreateInstance(..) instead of new StreamVideoClient(..). Reusing the client instance preserves call accept/decline states changed while the app was in the background. The getOrCreateInstance method ensures the same user reuses the existing instance.
Set android.smallIcon for best results. Use Android Asset Studio to generate icons with correct settings.
Initialize SDK push notification methods
Call configuration methods outside the application cycle, alongside AppRegistry.registerComponent(). This ensures configuration is available when the app opens from a push notification:
import { AppRegistry } from "react-native";
import { setPushConfig } from "src/utils/setPushConfig";
import { setFirebaseListeners } from "src/utils/setFirebaseListeners";
import App from "./App";
setPushConfig(); // Set push config
setFirebaseListeners(); // Set the firebase listeners
AppRegistry.registerComponent("app", () => App);Disabling push notifications
Disable push on user logout or user switch:
import { StreamVideoRN } from "@stream-io/video-react-native-sdk";
await StreamVideoRN.onPushLogout();Optional: Android full-screen incoming call view on locked phone
Display full-screen notification on locked phones by adding to onCreate in MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
// ..the rest
super.onCreate()
// Add this
StreamVideoReactNative.setupCallActivity(this)
}@Override
protected void onCreate(Bundle savedInstanceState) {
// ..the rest
super.onCreate();
// Add this
StreamVideoReactNative.setupCallActivity(this);
}Add permission to AndroidManifest.xml:
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />For apps installed on phones running versions Android 13 or lower, the USE_FULL_SCREEN_INTENT permission is enabled by default.
For all apps being installed on Android 14 and above, the Google Play Store revokes the USE_FULL_SCREEN_INTENT for apps that do not have calling or alarm functionalities. Which means, while submitting your app to the play store, if you do declare that 'Making and receiving calls' is a 'core' functionality in your app, this permission is granted by default on Android 14 and above.
If the USE_FULL_SCREEN_INTENT permission is not granted, the notification will show up as an expanded heads up notification on the lock screen.
Incoming and Outgoing call UI in foreground
Show call UIs during ringing calls. See watching for calls for implementation.
Troubleshooting
See the Troubleshooting guide for common issues.
- Add push provider credentials to Stream
- Install Dependencies
- iOS-specific setup
- Android-specific setup
- Request notification permissions
- Setup the push notifications configuration for the SDK
- Initialize SDK push notification methods
- Disabling push notifications
- Optional: Android full-screen incoming call view on locked phone
- Incoming and Outgoing call UI in foreground
- Troubleshooting

