> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tappd.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Push Notifications for React Native: FCM and APNs Setup

> Register iOS and Android push tokens with Tappd, track notification opens, and manage push subscription status using Firebase Cloud Messaging and APNs.

Tappd lets you register device push tokens from Firebase Cloud Messaging (FCM) on Android and Apple Push Notification Service (APNs) on iOS, then send targeted push notifications through the Tappd platform. This guide walks you through requesting permissions, retrieving tokens, registering them with the SDK, and handling incoming notifications on both platforms.

## Prerequisites

Before setting up push notifications, make sure you have:

1. **Firebase Cloud Messaging (FCM)** configured for Android
2. **Apple Push Notification Service (APNs)** configured for iOS
3. Push notification certificates or authentication keys uploaded to Firebase Console
4. A user identified via `tappd.identify()` — push tokens are always associated with an identified user

<Warning>
  You must call `tappd.identify()` before registering a push token.
  Attempting to register a token for an anonymous session will fail.
</Warning>

## Installation

Install the React Native Firebase messaging package and its peer dependency:

<CodeGroup>
  ```bash React Native Firebase (recommended) theme={null}
  npm install @react-native-firebase/app @react-native-firebase/messaging
  cd ios && pod install && cd ..
  ```

  ```bash Alternative theme={null}
  npm install react-native-push-notification
  ```
</CodeGroup>

## Setup

<Steps>
  <Step title="Request permissions">
    On iOS you must explicitly request permission before you can receive
    notifications. On Android, FCM handles permission automatically.

    ```javascript theme={null}
    import messaging from '@react-native-firebase/messaging';
    import { Platform } from 'react-native';

    async function requestPushPermission() {
      if (Platform.OS === 'ios') {
        const authStatus = await messaging().requestPermission();
        const enabled =
          authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
          authStatus === messaging.AuthorizationStatus.PROVISIONAL;
        return enabled;
      } else {
        // Android — FCM requests permissions automatically
        return true;
      }
    }
    ```
  </Step>

  <Step title="Get the push token">
    Retrieve the FCM token that identifies this device:

    ```javascript theme={null}
    import messaging from '@react-native-firebase/messaging';

    async function getPushToken() {
      try {
        const token = await messaging().getToken();
        return token;
      } catch (error) {
        console.error('Failed to get push token:', error);
        return null;
      }
    }
    ```
  </Step>

  <Step title="Register the token with Tappd">
    Pass the token and the platform string to `registerPushToken()`:

    ```javascript theme={null}
    import TappdSDK from '@tappd/mobile-sdk';
    import messaging from '@react-native-firebase/messaging';
    import { Platform } from 'react-native';

    const tappd = new TappdSDK({ appId: 'YOUR_APP_ID' });

    // Identify the user first
    await tappd.identify({
      external_id: 'user_123',
      email: 'john@example.com',
    });

    // Get and register the token
    const token = await messaging().getToken();
    if (token) {
      await tappd.registerPushToken(
        token,
        Platform.OS === 'ios' ? 'ios' : 'android'
      );
    }
    ```
  </Step>
</Steps>

## Complete Setup Example

Here's a full `App` component that wires up all three steps and handles token refresh in one place:

```javascript theme={null}
import React, { useEffect } from 'react';
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';
import TappdSDK from '@tappd/mobile-sdk';

const tappd = new TappdSDK({ appId: 'YOUR_APP_ID' });

function App() {
  useEffect(() => {
    setupPushNotifications();
  }, []);

  async function setupPushNotifications() {
    try {
      // Request permission on iOS
      if (Platform.OS === 'ios') {
        const authStatus = await messaging().requestPermission();
        const enabled =
          authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
          authStatus === messaging.AuthorizationStatus.PROVISIONAL;

        if (!enabled) {
          console.log('Push notifications not authorized');
          return;
        }
      }

      // Get token and register with Tappd
      const token = await messaging().getToken();
      if (token) {
        await tappd.registerPushToken(
          token,
          Platform.OS === 'ios' ? 'ios' : 'android'
        );
        console.log('Push token registered');
      }

      // Re-register automatically when the token rotates
      messaging().onTokenRefresh(async (newToken) => {
        await tappd.registerPushToken(
          newToken,
          Platform.OS === 'ios' ? 'ios' : 'android'
        );
        console.log('Push token refreshed');
      });
    } catch (error) {
      console.error('Push notification setup failed:', error);
    }
  }

  return (
    // Your app
  );
}
```

## Check Subscription Status

After registering a token, you can verify the user's subscription state:

```javascript theme={null}
async function checkSubscription() {
  try {
    const status = await tappd.checkPushSubscription();

    console.log('Subscribed:', status.subscribed);
    console.log('Status:', status.status); // 'subscribed', 'opted_in', or 'unsubscribed'
    console.log('Device Count:', status.deviceCount);

    return status;
  } catch (error) {
    console.error('Failed to check subscription:', error);
    return null;
  }
}
```

## Handle Token Refresh

FCM occasionally rotates push tokens. Always listen for refresh events and re-register so your user's token stays current:

```javascript theme={null}
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';

messaging().onTokenRefresh(async (newToken) => {
  try {
    await tappd.registerPushToken(
      newToken,
      Platform.OS === 'ios' ? 'ios' : 'android'
    );
    console.log('Token refreshed and registered');
  } catch (error) {
    console.error('Failed to register refreshed token:', error);
  }
});
```

## Handle Push Notifications

<Tabs>
  <Tab title="Foreground">
    Handle messages while the app is in the foreground using `onMessage()`:

    ```javascript theme={null}
    import messaging from '@react-native-firebase/messaging';

    messaging().onMessage(async (remoteMessage) => {
      console.log('Foreground message:', remoteMessage);

      // Track receipt
      await tappd.track('push_notification.received', {
        notificationId: remoteMessage.messageId,
        title: remoteMessage.notification?.title,
        body: remoteMessage.notification?.body,
      });

      // Show your custom in-app notification UI here
    });
    ```
  </Tab>

  <Tab title="Background">
    Register a background handler in your `index.js` entry file. This runs in a
    separate context, so the main SDK instance may not be available.

    ```javascript theme={null}
    // index.js
    import messaging from '@react-native-firebase/messaging';

    messaging().setBackgroundMessageHandler(async (remoteMessage) => {
      console.log('Background message:', remoteMessage);

      // The SDK instance may not be initialized here.
      // If you need to track receipt, call the Tappd API directly.
    });
    ```

    <Note>
      Register `setBackgroundMessageHandler()` in `index.js` before
      `AppRegistry.registerComponent()`. It must be registered at module load
      time, not inside a React component.
    </Note>
  </Tab>

  <Tab title="Notification taps">
    Track when users open the app by tapping a notification. Handle both
    cold-start (app was closed) and warm-start (app was backgrounded) cases:

    ```javascript theme={null}
    import messaging from '@react-native-firebase/messaging';

    // Cold start — app launched by tapping the notification
    messaging()
      .getInitialNotification()
      .then(async (remoteMessage) => {
        if (remoteMessage) {
          await tappd.trackPushOpen(remoteMessage);
        }
      });

    // Warm start — app was already running or backgrounded
    messaging().onNotificationOpenedApp(async (remoteMessage) => {
      await tappd.trackPushOpen(remoteMessage);
    });
    ```
  </Tab>
</Tabs>

## Platform-Specific Setup

<Tabs>
  <Tab title="iOS">
    ### 1. Enable the Push Notifications capability

    In Xcode:

    1. Open your project and select your app target.
    2. Go to **Signing & Capabilities**.
    3. Click **+ Capability**.
    4. Add **Push Notifications**.

    ### 2. Configure APNs in Firebase Console

    1. Open **Firebase Console → Project Settings → Cloud Messaging**.
    2. Under the **Apple app configuration** section, upload your **APNs Authentication Key** (`.p8`) or **APNs Certificate** (`.p12`).

    <Tip>
      APNs Authentication Keys (`.p8`) are preferred over certificates — they
      don't expire and work across all your apps.
    </Tip>
  </Tab>

  <Tab title="Android">
    ### 1. Add the Firebase configuration file

    Download `google-services.json` from the Firebase Console and place it in
    `android/app/`.

    ### 2. Configure the project-level `build.gradle`

    ```gradle theme={null}
    // android/build.gradle
    dependencies {
      classpath 'com.google.gms:google-services:4.3.15'
    }
    ```

    ### 3. Apply the plugin in the app-level `build.gradle`

    ```gradle theme={null}
    // android/app/build.gradle
    apply plugin: 'com.google.gms.google-services'
    ```

    <Note>
      After modifying Gradle files, run `cd android && ./gradlew clean` to
      clear the build cache before rebuilding.
    </Note>
  </Tab>
</Tabs>

## Troubleshooting

<Accordion title="Token not registering">
  If `registerPushToken()` throws an error or the token doesn't appear in the
  Tappd dashboard:

  1. **Ensure `identify()` was called first** — the SDK requires a known user before it can associate a device token.
  2. **Check FCM/APNs configuration** — on iOS, confirm the APNs key or certificate is uploaded to Firebase Console. On Android, confirm `google-services.json` is in `android/app/`.
  3. **Verify network connectivity** — token registration requires an active internet connection.
  4. **Check your error logs** — wrap `registerPushToken()` in try-catch and log any caught errors.
</Accordion>

<Accordion title="Notifications not being received">
  If push notifications aren't arriving on the device:

  1. **Verify the token is registered** — check the Tappd dashboard to confirm the device appears under the user's profile.
  2. **Check subscription status** — call `tappd.checkPushSubscription()` and inspect the `status` field.
  3. **Verify FCM/APNs setup** — use the Firebase Console's test message tool to send a direct test notification to the token.
  4. **Check device permissions** — on iOS, go to Settings → Notifications and confirm notifications are allowed for your app.
</Accordion>

## Best Practices

* **Identify users before registering tokens** — push tokens are tied to users; always call `identify()` first.
* **Always listen for token refresh** — FCM can rotate tokens at any time. A stale token means missed notifications.
* **Track opens on both cold and warm starts** — handle both `getInitialNotification()` and `onNotificationOpenedApp()` to get complete open-rate data.
* **Wrap everything in try-catch** — token retrieval and registration can fail due to network issues or misconfigured credentials.
* **Use `Platform.OS`** — always pass `'ios'` or `'android'` to `registerPushToken()` so Tappd routes notifications through the correct provider.
