---
title: Step 5. Implementing the Push client for React Native
description: Explains how to use the Push SDK to handle push notifications in your React Native app.
component: orchsdks
page_id: orchsdks:journey:use-cases/push/react-native/05_implement_push_client
canonical_url: https://developer.pingidentity.com/orchsdks/journey/use-cases/push/react-native/05_implement_push_client.html
llms_txt: https://developer.pingidentity.com/orchsdks/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
revdate: Sun, 15 Jun 2026 00:00:00 +0000
keywords: ["Push", "MFA", "Strong Auth", "Integration", "SDK", "mobile", "authentication", "notification", "React Native", "FCM", "APNs"]
section_ids:
  adding_core_dependencies: Adding core dependencies
  initializing_the_push_client: Initializing the Push client
  using_the_pushprovider_and_usepush_hook: Using the PushProvider and usePush hook
  using_the_factory_function: Using the factory function
  config-options: Configuration options
  registering_for_push_notifications_and_handling_device_tokens: Registering for push notifications and handling device tokens
  requesting_permission_and_obtaining_the_device_token_on_android: Requesting permission and obtaining the device token on Android
  requesting_permission_and_obtaining_the_device_token_on_ios: Requesting permission and obtaining the device token on iOS
  subscribe-token-reg: Subscribing to token registration events
  updating_device_tokens: Updating device tokens
  getting_the_current_device_token: Getting the current device token
  refreshing_the_device_token_on_android: Refreshing the device token on Android
  registering_a_device_using_an_authentication_journey: Registering a device using an authentication journey
  managing_push_credentials: Managing Push credentials
  getting_push_credentials: Getting Push credentials
  updating_push_credentials: Updating Push credentials
  deleting_push_credentials: Deleting Push credentials
  responding_to_push_notifications: Responding to push notifications
  processing_incoming_messages: Processing incoming messages
  responding_to_tap_to_accept_notifications: Responding to tap to accept notifications
  responding_to_display_challenge_code_notifications: Responding to display challenge code notifications
  responding_to_biometrics_to_accept_notifications: Responding to biometrics to accept notifications
  managing_stored_notifications: Managing stored notifications
  getting_pending_notifications: Getting pending notifications
  getting_all_stored_notifications: Getting all stored notifications
  cleanup: Cleaning up stored notifications
  authenticating_with_push_notifications_in_a_journey: Authenticating with push notifications in a journey
  handling_errors: Handling errors
---

# Step 5. Implementing the Push client for React Native

[icon: circle-check, set=far]PingOne Advanced Identity Cloud [icon: circle-check, set=far]PingAM [icon: react, set=fab]React Native

This page guides you through implementing the **Push** client in your React Native application to support Push-based Multi-Factor Authentication (MFA).

It covers dependency setup, Push client initialization, credential management, handling different push notification types, and custom storage options.

## Adding core dependencies

To add the **Push** module to your React Native project:

* npm

* yarn

```bash
npm install @ping-identity/rn-push @ping-identity/rn-core
```

```bash
yarn add @ping-identity/rn-push @ping-identity/rn-core
```

The **Push** module also requires the React Native Firebase packages, which handle FCM token delivery on Android and provide the permission-request API on iOS:

* npm

* yarn

```bash
npm install @react-native-firebase/app @react-native-firebase/messaging
```

```bash
yarn add @react-native-firebase/app @react-native-firebase/messaging
```

Because these packages include native iOS code, run `pod install` in the `ios` directory after installing them:

```bash
cd ios && pod install
```

## Initializing the Push client

The **Push** module provides two patterns for initializing and managing the Push client: a React context provider for component-tree access, and a standalone factory function for imperative use.

### Using the PushProvider and usePush hook

Wrapping your application with `PushProvider` makes the Push client available throughout your component tree by using the `usePush` hook. This is the recommended approach for most applications.

The optional `config` prop accepts the same options as `createPushClient()`. See [Configuration options](#config-options) for the full list.

Wrapping your app with `PushProvider`

```typescript
import { PushProvider } from '@ping-identity/rn-push';

function App() {
  return (
    <PushProvider config={{ timeoutMs: 20000, notificationCleanupConfig: { cleanupMode: 'HYBRID' } }}>
      <YourNavigator />
    </PushProvider>
  );
}
```

Accessing the Push client in a component

```typescript
import { usePush } from '@ping-identity/rn-push';

function PushScreen() {
  const [data, { loading, error, refresh }] = usePush();

  if (loading) return <ActivityIndicator />;
  if (error) return <Text>{error.message}</Text>;
  if (!data) return null;

  const { client, credentials, deviceToken, pendingNotifications } = data;

  // Render your push MFA UI here:
  // - 'client' to approve/deny notifications
  // - 'credentials' to list registered devices
  // - 'pendingNotifications' to show outstanding requests
}
```

### Using the factory function

For cases where you need direct control over the Push client lifecycle, create a client with `createPushClient()`.

When you're done with the client, call `close()` to clean up any temporary files and free memory.

Creating a Push client directly

```typescript
import { createPushClient } from '@ping-identity/rn-push';

const pushClient = await createPushClient({
  enableCredentialCache: false,
  timeoutMs: 15000,
});

try {
  // ... use pushClient
} finally {
  // Close the client when done
  await pushClient.close();
}
```

### Configuration options

The properties you can use to customize the Push client configuration are as follows:

* *enableCredentialCache*

  Whether to enable in-memory caching of credentials.

  By default, this is set to `false` for security reasons, as an attacker could potentially access cached credentials from memory dumps.

* *timeoutMs*

  The timeout for network operations, in milliseconds.

  The default value is `15000` ms (15 seconds).

* *storage*

  The storage configuration to use for Push credentials and notifications.

  If not provided, the default platform storage is used (SQLite on Android, Keychain on iOS).

  Learn more in [Customizing Journey module storage in React Native](../../../customization/storage/customize-react-native-storage.html).

* *notificationCleanupConfig*

  Optional configuration for the automatic cleanup of push notifications.

  Learn more in [Cleaning up stored notifications](#cleanup).

* *logger*

  The logger instance used for logging messages.

  Defaults to the global logger instance.

  Learn more in [Configuring logging in React Native](../../../customization/logging/react-native-custom-logging.html).

* *ios*

  iOS-specific configuration options.

  If not provided, default values are used for all iOS-specific settings.

  Currently accepts `encryptionEnabled` (boolean, defaults to `true`).

## Registering for push notifications and handling device tokens

Before the **Push** module can receive push notifications, your app must request permission from the operating system and forward device tokens to the Push client.

### Requesting permission and obtaining the device token on Android

On Android, Firebase Cloud Messaging delivers a device token automatically after the app connects to Firebase. Use `@react-native-firebase/messaging` to obtain and forward the token:

Registering for FCM tokens on Android

```typescript
import messaging from '@react-native-firebase/messaging';

// Request notification permission (Android 13+)
const authStatus = await messaging().requestPermission();
const enabled =
  authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
  authStatus === messaging.AuthorizationStatus.PROVISIONAL;

if (enabled) {
  // Get the current FCM token and register it with the Push client
  const token = await messaging().getToken();
  await pushClient.setDeviceToken(token);

  // Listen for token refresh events and update the Push client
  const unsubscribe = messaging().onTokenRefresh(async (newToken) => {
    await pushClient.setDeviceToken(newToken);
  });
}
```

### Requesting permission and obtaining the device token on iOS

On iOS, the APNs device token is registered automatically by the native SDK through the `AppDelegate`. Your app must request notification permission before APNs will deliver any tokens or notifications, but the token itself is forwarded to the Push client by the native layer, so no manual `setDeviceToken()` call is needed.

Requesting notification permission on iOS

```typescript
import messaging from '@react-native-firebase/messaging';

// Request notification permission (required before APNs will deliver notifications)
const authStatus = await messaging().requestPermission();
const enabled =
  authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
  authStatus === messaging.AuthorizationStatus.PROVISIONAL;

if (!enabled) {
  // User denied permission; push notifications will not be delivered
  showPermissionDeniedMessage();
}
```

The Push client's [`onTokenRegistered` subscription](#subscribe-token-reg) fires when the native layer has forwarded the APNs token, giving you a reliable signal that the client is ready to receive push notifications.

### Subscribing to token registration events

The **Push** module also emits an event when the native layer has registered a device token.

Subscribe to this event to keep the Push client in sync:

Subscribing to token events

```typescript
useEffect(() => {
  const unsubscribe = pushClient.onTokenRegistered((token) => {
    // Token has been registered or refreshed
    console.log('Token registered:', token);
  });

  return unsubscribe;
}, [pushClient]);
```

### Updating device tokens

Under certain circumstances, the client operating system issues a new device token that your app needs to use for receiving push notifications.

> **Collapse: What can cause the device token for push messages to change?**
>
> The device token used to receive push messages can change due to a number of circumstances:
>
> * Uninstalling and reinstalling the client app
>
>   If the user uninstalls and then reinstalls the app, the OS regenerates the device token.
>
>   This is one of the most common reasons for a token change.
>
> * Clearing app data
>
>   Clearing the application's data by using the device settings causes the OS to issue a new device token upon next launch of the app.
>
> * Revoking and regranting Push permission
>
>   The OS might issue a new device token if a user revokes and then re-enables push notifications.
>
> * Push services expiring or invalidating tokens
>
>   The push services themselves, such as the Apple Push Notification service (APNs) or Google's Firebase Cloud Messaging (FCM) service might invalidate device tokens for various reasons.
>
>   The OS issues a new device token upon next launch of the app if the push service invalidates the existing tokens.
>
> * Updating the operating system
>
>   Occasionally, OS updates, especially major versions, might result in the push notification service issuing a new token.
>
>   Updating the OS can also clear app data, which would also mean the app requires a new device token on next launch.
>
> * Updating or migrating apps
>
>   If you change the package or bundle IDs of your client app that uses push notification, or alter the signing keys, the OS might invalidate existing device tokens and issue a new one.
>
>   Similarly, if the user restores the app from backup, or migrates the app to a different device, the OS might issue a new device token, even if restoring the app to the same physical device.

The **Push** module provides methods for updating the device token associated with accounts it has registered to receive Push notifications. These methods also contact the server that registered the device to update its copy of the device token.

Failing to update the device token on both the client and the server will prevent push messages from arriving, which will cause authentication to fail.

|   |                                                                                                                      |
| - | -------------------------------------------------------------------------------------------------------------------- |
|   | Updating existing accounts with a new device token is only supported by the following server:- PingAM 8.0.1 or later |

Updating the device token

```typescript
// Update the token globally across all registered credentials
await pushClient.setDeviceToken(newToken);

// Update the token for a specific credential only
await pushClient.setDeviceToken(newToken, credentialId);
```

### Getting the current device token

Use the `getDeviceToken()` method to retrieve the device token currently stored by the Push client:

Getting the current device token

```typescript
const token = await pushClient.getDeviceToken();

if (token) {
  console.log('Current device token:', token);
} else {
  // No token registered yet
}
```

### Refreshing the device token on Android

On Android, you can use `refreshToken()` to explicitly fetch the current FCM registration token and register it with the Push client.

This is useful when automatic token delivery has not yet completed, for example when the app starts before the Firebase connection is established.

Refreshing the FCM token on Android

```typescript
// Android only; on iOS this is a no-op (APNs token is delivered through AppDelegate)
const token = await pushClient.refreshToken();

if (token) {
  console.log('Token refreshed:', token);
}
```

## Registering a device using an authentication journey

Before a user can receive push notifications, their device must be registered with the server. Registration is typically done as part of an enrollment journey that contains the [Push Registration node](https://docs.pingidentity.com/auth-node-ref/latest/push-registration.html).

When the server reaches the Push Registration node, it returns a `PollingWaitCallback`. This callback carries the `pushauth://` URI that the **Push** module needs to create the device credentials. The journey then pauses while the registration completes.

Your application needs to:

1. Extract the URI from the `PollingWaitCallback` message field.

2. Call `addCredentialFromUri()` with that URI to register the device.

3. Pass the callbacks back unchanged to advance the journey past the registration step.

Registering a device during a push enrollment journey

```typescript
import { useJourney } from '@ping-identity/rn-journey';
import { usePush, PushError } from '@ping-identity/rn-push';

function PushEnrollmentScreen() {
  const [node, actions] = useJourney();
  const [pushData] = usePush();

  useEffect(() => {
    actions.start('MFAwithPush');
  }, []);

  useEffect(() => {
    async function handlePushRegistrationNode() {
      if (node?.type !== 'ContinueNode') return;
      if (!pushData?.client) return;

      const pollingCallback = node.callbacks?.find(
        cb => cb.type === 'PollingWaitCallback',
      );

      if (!pollingCallback) return;

      // The Push Registration node embeds the pushauth:// URI in the message field
      const uri = pollingCallback.message;

      if (!uri?.startsWith('pushauth://')) return;

      try {
        await pushData.client.addCredentialFromUri(uri);

        // Device registered; advance the journey with an empty payload
        await actions.next({ callbacks: node.callbacks ?? [] });
      } catch (error) {
        if (error instanceof PushError) {
          if (error.code === 'duplicate_credential') {
            // Device is already registered; still advance the journey
            await actions.next({ callbacks: node.callbacks ?? [] });
          } else {
            showError(`Registration failed: ${error.message}`);
          }
        }
      }
    }

    void handlePushRegistrationNode();
  }, [node, pushData?.client]);

  if (actions.loading) return <ActivityIndicator />;

  if (node?.type === 'SuccessNode') {
    return <Text>Device registered successfully.</Text>;
  }

  if (node?.type === 'ErrorNode') {
    return <Text>{node.message}</Text>;
  }

  if (node?.type === 'FailureNode') {
    return <Text>Something went wrong. Please try again.</Text>;
  }

  return null;
}
```

## Managing Push credentials

The **Push** module stores a credential for each registered device and account pair. You can retrieve, update, or remove these credentials as needed.

For example, your app could display registered devices on an account settings screen, let users rename a credential, or allow them to remove a device they no longer use.

### Getting Push credentials

You can get a list of all registered Push credentials or get an individual credential by passing its ID as a parameter.

* All Push credentials

* Specific Push credential

Getting all Push credentials

```typescript
const credentials = await pushClient.getCredentials();

if (credentials.length === 0) {
  showMessage('No credentials found');
} else {
  credentials.forEach(credential => {
    const displayName = credential.displayAccountName ?? credential.accountName;
    const displayOrg  = credential.displayIssuer ?? credential.issuer;
    const logoUrl     = credential.imageURL;
    const bgColor     = credential.backgroundColor;
    const locked      = credential.isLocked;

    // Render each credential as a list item, e.g.:
    // "Babs Jensen / Example.com" with the org logo and a locked badge if locked
    renderCredentialRow({ displayName, displayOrg, logoUrl, bgColor, locked });
  });
}
```

Getting a specific Push credential

```typescript
const credential = await pushClient.getCredential(credentialId);

if (credential !== null) {
  const displayName = credential.displayAccountName ?? credential.accountName;
  const displayOrg  = credential.displayIssuer ?? credential.issuer;
  const registeredAt = new Date(credential.createdAt).toLocaleDateString();

  showCredentialDetail({ displayName, displayOrg, registeredAt });
} else {
  showMessage('Credential not found');
}
```

### Updating Push credentials

You can update the display properties of a stored credential with new values by using the `saveCredential()` method. Pass the updated credential object into the method as a parameter:

Updating a Push credential

```typescript
const updated = {
  ...credential,
  displayAccountName: 'Babs Jensen',
  displayIssuer: 'Example.com Checking Account',
};

await pushClient.saveCredential(updated);
```

### Deleting Push credentials

Use the `deleteCredential()` method to remove individual credentials from the client device. Pass the credential ID into the method as a parameter:

Deleting a Push credential

```typescript
const deleted = await pushClient.deleteCredential(credentialId);

if (deleted) {
  showMessage('Credential removed');
} else {
  showMessage('Credential not found');
}
```

## Responding to push notifications

When your app receives a push notification from Firebase (Android) or APNs (iOS), you need to forward the message payload to the **Push** module for processing.

### Processing incoming messages

Processing incoming push messages

```typescript
import messaging from '@react-native-firebase/messaging';

// Handle background messages (Android and iOS)
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
  const notification = await pushClient.processNotification(remoteMessage.data);

  if (notification) {
    // Notification stored; user will respond when they open the app
  }
});

// Handle foreground messages
useEffect(() => {
  const unsubscribe = messaging().onMessage(async (remoteMessage) => {
    const notification = await pushClient.processNotification(remoteMessage.data);

    if (notification) {
      const message = notification.messageText ?? 'Authentication request received';

      switch (notification.pushType) {
        case 'default':
          showDefaultNotification({ id: notification.id, message });
          break;
        case 'challenge':
          showChallengeNotification({ id: notification.id, message });
          break;
        case 'biometric':
          showBiometricPrompt({ id: notification.id, message });
          break;
      }
    }
  });

  return unsubscribe;
}, [pushClient]);
```

You can also subscribe to the Push module's notification event to react to notifications processed by other parts of your app:

Subscribing to the Push module's notification stream

```typescript
useEffect(() => {
  const unsubscribe = pushClient.onNotification((notification) => {
    // Notification has been processed and stored
    if (notification) showNotificationUI(notification);
  });

  return unsubscribe;
}, [pushClient]);
```

If your app receives push payloads as raw strings or JWTs rather than as dictionary objects, use `processNotificationFromMessage()` instead:

Processing a push notification from a raw string payload

```typescript
const notification = await pushClient.processNotificationFromMessage(rawMessage);

if (notification) {
  // Handle the notification as usual
}
```

The **Push** module supports three push notification types depending on your use case and provides methods for handling each one.

### Responding to tap to accept notifications

The **Tap to accept** notification type displays an **Accept** and a **Reject** button for the user to choose how to proceed.

This is the default notification type.

Call the `approveNotification()` method to approve the push notification, or `denyNotification()` to reject it:

Approving or denying tap to accept notifications

```typescript
// To approve
const approved = await pushClient.approveNotification(notificationId);
if (approved) {
  showMessage('Authentication approved');
}

// To deny
const denied = await pushClient.denyNotification(notificationId);
if (denied) {
  showMessage('Authentication denied');
}
```

### Responding to display challenge code notifications

The **Display Number Challenge** notification type displays a number that the user must match on their device, either by entering the value or selecting it from multiple options.

Use the `getNumbersChallenge()` utility to parse the challenge values from the notification, then use `approveChallengeNotification()` to return the user's selection to the server:

Handling display challenge code notifications

```typescript
import { getNumbersChallenge } from '@ping-identity/rn-push';

// Parse the challenge numbers from the notification
const options = getNumbersChallenge(notification); // e.g., [12, 34, 56]

// Display options to the user and capture their selection
// ...

// Submit the selected number as a string
const challengeResponse = String(userSelectedOption); // e.g., "34"

const approved = await pushClient.approveChallengeNotification(notificationId, challengeResponse);
if (approved) {
  showMessage('Authentication approved');
}
```

### Responding to biometrics to accept notifications

The **Use Biometrics to Accept** notification type initially displays the same **Accept** and a **Reject** buttons for the user to choose how to proceed.

If the user selects to accept the notification, the client device presents its biometric options for the user to authenticate with.

Use the `approveBiometricNotification()` method to respond to the notification after the user successfully authenticates:

Approving or denying biometrics to accept notifications

```typescript
// After the user authenticates with biometrics
const authMethod = 'fingerprint'; // or 'face', 'iris', etc.

const approved = await pushClient.approveBiometricNotification(notificationId, authMethod);
if (approved) {
  showMessage('Authentication approved');
}
```

## Managing stored notifications

The **Push** module stores all notifications, including any that are still pending a response.

### Getting pending notifications

You can get a list of pending notifications that have not yet been approved or rejected by using the `getPendingNotifications()` method:

Getting all pending notifications

```typescript
const notifications = await pushClient.getPendingNotifications();

if (notifications.length > 0) {
  notifications.forEach(notification => {
    const message    = notification.messageText ?? 'Authentication request received';
    const type       = notification.pushType;        // 'default' | 'challenge' | 'biometric'
    const receivedAt = new Date(notification.createdAt).toLocaleTimeString();

    renderPendingRow({ id: notification.id, message, type, receivedAt });
  });
} else {
  showEmptyState();
}
```

You can use the `getNotification()` method to get an individual notification by passing its ID as a parameter:

Getting an individual pending notification

```typescript
const notification = await pushClient.getNotification(notificationId);

if (notification !== null) {
  const message    = notification.messageText ?? 'Authentication request received';
  const type       = notification.pushType;
  const receivedAt = new Date(notification.createdAt).toLocaleTimeString();

  showNotificationDetails({ message, type, receivedAt });
} else {
  showMessage('Notification not found');
}
```

### Getting all stored notifications

Use the `getAllNotifications()` method to get every notification stored on the device, including those that have already been responded to:

Getting all stored notifications

```typescript
const notifications = await pushClient.getAllNotifications();

notifications.forEach(notification => {
  const message      = notification.messageText ?? 'Authentication request';
  const type         = notification.pushType;
  const receivedAt   = new Date(notification.createdAt).toLocaleDateString();
  const respondedAt  = notification.respondedAt
    ? new Date(notification.respondedAt).toLocaleDateString()
    : null;
  const status       = notification.pending ? 'Pending' : (notification.approved ? 'Approved' : 'Denied');

  renderHistoryRow({ message, type, receivedAt, respondedAt, status });
});
```

### Cleaning up stored notifications

The **Push** module provides automatic cleanup of push notifications.

This prevents your app from accumulating too many push notification records, which improves performance and reduces storage usage.

You can customize notification cleanup by providing a `notificationCleanupConfig` when initializing the Push client:

Automating cleanup of stored notifications

```typescript
const pushClient = await createPushClient({
  notificationCleanupConfig: {
    // Choose a cleanup mode: 'NONE', 'COUNT_BASED', 'AGE_BASED', or 'HYBRID'
    cleanupMode: 'HYBRID',
    // Maximum notifications to keep when using COUNT_BASED or HYBRID mode
    maxStoredNotifications: 50,
    // Maximum age in days when using AGE_BASED or HYBRID mode
    maxNotificationAgeDays: 14,
  },
});
```

The properties you can provide are as follows:

* **cleanupMode**

  The strategy the module uses to clean up notifications.

  Choose from one of the following:

  * `'NONE'`

    The module does not perform automatic cleanup of notifications.

  * `'COUNT_BASED'`

    The module keeps a maximum number of notifications and deletes the oldest first when the limit is exceeded.

    This is the default.

  * `'AGE_BASED'`

    The module deletes notifications that are older than the specified number of days.

  * `'HYBRID'`

    The module applies both count and age limits to the stored notifications.

* **maxStoredNotifications**

  The maximum number of notifications to keep, if using the `'COUNT_BASED'` mode.

  If the number is exceeded, the module deletes the oldest notifications first to reach the threshold.

  The default is `100`.

* **maxNotificationAgeDays**

  The maximum number of days to keep a notification before removing it.

  Defaults to `30`.

You can also trigger notification cleanup on-demand by using the `cleanupNotifications()` method:

Manually cleaning up stored notifications

```typescript
// Clean up notifications for all credentials
const removedCount = await pushClient.cleanupNotifications();
console.log(`Removed ${removedCount} old notifications`);

// Clean up notifications for a specific credential
const removedForCredential = await pushClient.cleanupNotifications(credentialId);
console.log(`Removed ${removedForCredential} old notifications for credential`);
```

## Authenticating with push notifications in a journey

When the server needs to verify a user's identity using push MFA, it sends a push notification to the user's registered device and pauses the authentication journey at the [Push Wait node](https://docs.pingidentity.com/auth-node-ref/latest/push-wait.html).

The flow works as follows:

1. The server receives a sign-in request and starts the authentication journey.

2. The [Push Sender node](https://docs.pingidentity.com/auth-node-ref/latest/push-sender.html) sends a push notification to the user's device.

3. The journey pauses at the [Push Wait node](https://docs.pingidentity.com/auth-node-ref/latest/push-wait.html), which returns a `ConfirmationCallback` and a `HiddenValueCallback` to your client.

4. The user receives and responds to the push notification on their device.

5. The **Push** module sends the user's decision directly to the server.

6. Your journey client polls the journey by calling `actions.next()` until the server resolves the wait and returns the next node.

While the journey is paused at the Push Wait node, your application receives two callbacks from the server:

* `ConfirmationCallback`

  Represents the decision the user is expected to make. The push notification itself collects the user's decision, so no in-app interaction is needed for this callback.

* `HiddenValueCallback`

  Carries internal state used by the server. Your client passes this back unchanged when advancing the journey.

Your application should poll the journey at a regular interval, calling `actions.next()` with the unchanged callbacks until the server advances the journey.

Handling the Push Wait node in a journey

```typescript
import { useJourney } from '@ping-identity/rn-journey';
import { useRef } from 'react';

function PushAuthScreen() {
  const [node, actions] = useJourney();
  const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);

  useEffect(() => {
    actions.start('MFAwithPush');
  }, []);

  useEffect(() => {
    if (node?.type !== 'ContinueNode') {
      // Clear any active polling when the journey advances
      if (pollingRef.current) {
        clearInterval(pollingRef.current);
        pollingRef.current = null;
      }
      return;
    }

    const confirmationCallback = node.callbacks?.find(
      cb => cb.type === 'ConfirmationCallback',
    );
    const hiddenValueCallback = node.callbacks?.find(
      cb => cb.type === 'HiddenValueCallback',
    );

    // Poll the Push Wait node until the server resolves the notification
    if (confirmationCallback && hiddenValueCallback) {
      const pollIntervalMs = 4000;

      pollingRef.current = setInterval(async () => {
        try {
          // Pass the callbacks back unchanged; the Push module has already
          // communicated the user's decision to the server directly
          await actions.next({ callbacks: node.callbacks ?? [] });
        } catch {
          // Journey may have already advanced; stop polling
          if (pollingRef.current) {
            clearInterval(pollingRef.current);
            pollingRef.current = null;
          }
        }
      }, pollIntervalMs);

      return () => {
        if (pollingRef.current) {
          clearInterval(pollingRef.current);
          pollingRef.current = null;
        }
      };
    }
  }, [node]);

  if (actions.loading) return <ActivityIndicator />;

  if (node?.type === 'ContinueNode') {
    const hasPushWait =
      node.callbacks?.some(cb => cb.type === 'ConfirmationCallback') &&
      node.callbacks?.some(cb => cb.type === 'HiddenValueCallback');

    if (hasPushWait) {
      return (
        <View>
          <Text>Check your device for a push notification.</Text>
          <ActivityIndicator />
        </View>
      );
    }
  }

  if (node?.type === 'SuccessNode') {
    return <Text>Authentication successful.</Text>;
  }

  if (node?.type === 'ErrorNode') {
    return <Text>{node.message}</Text>;
  }

  if (node?.type === 'FailureNode') {
    return <Text>Something went wrong. Please try again.</Text>;
  }

  return null;
}
```

|   |                                                                                                                                                                                                                                                                                                     |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The user's approval or denial of the notification is sent directly to the server by the **Push** module when the user responds to the notification.Your journey client does not need to set any value on the callbacks. It only needs to keep polling until the server resolves the Push Wait node. |

## Handling errors

The **Push** module throws `PushError` instances with a `code` property that identifies the specific failure:

Handling Push errors

```typescript
import { PushError } from '@ping-identity/rn-push';

try {
  await pushClient.addCredentialFromUri(uri);
} catch (error) {
  if (error instanceof PushError) {
    switch (error.code) {
      case 'invalid_uri':
        showError('The registration URI is invalid');
        break;
      case 'duplicate_credential':
        showError('This device is already registered');
        break;
      case 'network_failure':
        showError('Check your network connection and try again');
        break;
      case 'credential_locked':
        showError('This credential is locked by policy');
        break;
      default:
        showError(`Push error: ${error.message}`);
    }
  }
}
```

The full set of error codes is as follows:

* `credential_locked`

  The credential is locked by a policy.

* `credential_not_found`

  No credential exists with the specified ID.

* `device_token_not_set`

  No device token has been registered with the push service.

* `duplicate_credential`

  A credential for this account is already registered.

* `initialization_failed`

  The native layer failed to initialize.

* `invalid_parameter_value`

  A parameter value is outside the expected range or format.

* `invalid_platform`

  The platform string in the payload is unrecognized.

* `invalid_push_type`

  The push type string in the payload is unrecognized.

* `invalid_uri`

  The provided `pushauth://` URI is malformed.

* `message_parsing_failed`

  The incoming push payload could not be parsed.

* `missing_required_parameter`

  A required parameter was not provided.

* `network_failure`

  A network request failed.

* `no_handler_for_platform`

  No push handler is registered for the payload's platform.

* `not_initialized`

  The Push client has not been initialized.

* `notification_not_found`

  No notification exists with the specified ID.

* `policy_violation`

  A push action was blocked by a server policy.

* `registration_failed`

  The server rejected the device registration.

* `storage_failure`

  A credential or notification storage operation failed.
