React Native Push Notifications

TypeScript, RN 0.71+. Built on @react-native-firebase/messaging and react-native-callkeep.

⚛️ React Native

TypeScript, RN 0.71+. Built on @react-native-firebase/messaging and react-native-callkeep.

Download the SDK from the dashboard (Integration guide) after you create a free account.

  1. Install

    Unzip the download into your project's root folder (this creates PushConsoleSDK/), set up Firebase following rnfirebase.io, then:

    Terminal
    npm install ./PushConsoleSDK/react-native/push-console \
      @react-native-firebase/app @react-native-firebase/messaging \
      react-native-callkeep react-native-voip-push-notification \
      @react-native-async-storage/async-storage react-native-device-info
    cd ios && pod install

    React Native 0.71+. Expo needs a development build (it doesn't run in Expo Go).

  2. Background handler in index.js

    index.js
    // index.js — register BEFORE AppRegistry
    import { AppRegistry } from 'react-native';
    import messaging from '@react-native-firebase/messaging';
    import PushConsole from '@pushconsole/react-native';
    import App from './App';
    import { name as appName } from './app.json';
    
    messaging().setBackgroundMessageHandler(async (message) => {
      await PushConsole.handleBackgroundMessage(message); // Android: show the call screen
    });
    
    AppRegistry.registerComponent(appName, () => App);
  3. Initialize in the root component

    App.tsx
    import { useEffect } from 'react';
    import PushConsole from '@pushconsole/react-native';
    
    export default function App() {
      useEffect(() => {
        PushConsole.initialize(
          { appId: 'YOUR_APP_ID', serverUrl: 'https://tingpush.com', appName: 'MyApp' },
          {
            onCallEvent: ({ action, call }) => {
              if (action === 'accepted') {
                // User tapped Answer → connect the call (call.id, call.data.room)
              }
            },
            onNotificationOpened: (data) => { /* navigate using data */ },
          },
        );
      }, []);
      // ...
    }
  4. Assign the user at login

    TypeScript
    await PushConsole.login('user-123');       // after login
    await PushConsole.logout();                // on logout
    await PushConsole.setTags({ plan: 'vip' });
    PushConsole.endCall(callId);               // end the call
  5. iOS: enable capabilities and update AppDelegate (for calls)

    Xcode → Signing & Capabilities: Push Notifications and Background Modes (Remote notifications + Voice over IP). VoIP pushes must be reported to CallKit in native code:

    ios/AppDelegate.mm
    // AppDelegate.mm
    #import <PushKit/PushKit.h>
    #import "RNCallKeep.h"
    #import "RNVoipPushNotificationManager.h"
    
    @interface AppDelegate () <PKPushRegistryDelegate>
    @end
    
    // In didFinishLaunchingWithOptions, before return:
    //   [RNVoipPushNotificationManager voipRegistration];
    
    - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
      [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];
    }
    
    - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
                 forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
      NSDictionary *p = payload.dictionaryPayload;
      NSString *uuid = p[@"call_id"] ?: [[NSUUID UUID] UUIDString];
      NSString *name = p[@"caller_name"] ?: @"";
      NSString *handle = p[@"caller_handle"] ?: name;
      BOOL hasVideo = [p[@"is_video"] isEqualToString:@"true"];
    
      [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
      [RNCallKeep reportNewIncomingCall:uuid handle:handle handleType:@"generic" hasVideo:hasVideo
                    localizedCallerName:name supportsHolding:NO supportsDTMF:NO supportsGrouping:NO
                     supportsUngrouping:NO fromPushKit:YES payload:p withCompletionHandler:completion];
    }

    AppDelegate.swift (RN 0.77+): do the same and add the headers to your bridging header.

  6. Android: declare the ConnectionService

    android/app/src/main/AndroidManifest.xml
    <uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    
    <application>
      <service android:name="io.wazo.callkeep.VoiceConnectionService"
          android:label="Wazo"
          android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
          android:foregroundServiceType="camera|microphone"
          android:exported="true">
        <intent-filter><action android:name="android.telecom.ConnectionService" /></intent-filter>
      </service>
      <service android:name="io.wazo.callkeep.RNCallKeepBackgroundMessagingService" />
    </application>

    On first run Android asks for the "calling accounts" permission; some phones need it enabled under Settings → Calling apps.

✅ After integrating, check

  1. Run the app on a real device with logging on: you should see [PushConsole] Device registered ….
  2. The project's Devices tab: the device appears; iOS devices show the VoIP badge if you use calls.
  3. The Send tab: send a test to that device.
  4. Choose 📞 Call and test with the app open, closed and with the screen locked.

Ready to integrate?

Create an account to get your App ID and API Key and download the SDKs. Dashboard samples are pre-filled with your project details.