---
title: Resuming journeys using magic links in React Native
description: Learn how to suspend and resume authentication journeys using magic links in your React Native application.
component: orchsdks
page_id: orchsdks:journey:use-cases/magic-links/react-native-magic-links
canonical_url: https://developer.pingidentity.com/orchsdks/journey/use-cases/magic-links/react-native-magic-links.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: Fri, 12 Jun 2026 14:24:00 +0000
keywords: ["PingOne Advanced Identity Cloud", "PingAM", "Journeys", "React Native", "Magic Links", "Suspended Authentication", "Resume", "Setup &amp; Configuration", "Source Code", "Use Case", "SDK"]
section_ids:
  installing_the_sdk_packages: Installing the SDK packages
  configuring_a_custom_uri_scheme_for_deep_linking: Configuring a custom URI scheme for deep linking
  registering_a_uri_scheme_on_android: Registering a URI scheme on Android
  registering_a_uri_scheme_on_ios: Registering a URI scheme on iOS
  creating_the_journey_client: Creating the Journey client
  detecting_the_suspended_journey: Detecting the suspended journey
  handling_the_incoming_deep_link_and_resuming_the_journey: Handling the incoming deep link and resuming the journey
  handling_errors: Handling errors
---

# Resuming journeys using magic links in React Native

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

You can configure your React Native app to capture the URI of the magic link the user visits. The key part of this deep-link URI is the `suspendedId` query parameter.

You can then use the **Journey** client's `resume()` method, rather than `start()`, to continue the journey. You must pass in the URI that contains the `suspendedId` parameter.

## Installing the SDK packages

To install the module into your React Native project, use `yarn` or `npm` as follows:

* yarn

* npm

```shell
yarn add @ping-identity/rn-journey
```

```shell
npm install @ping-identity/rn-journey
```

Because this package includes native iOS code, run `pod install` in the `ios` directory after installing it:

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

## Configuring a custom URI scheme for deep linking

Magic links encode the `suspendedId` as a query parameter in the URI that opens your app.

You must register a custom URI scheme in the Android and iOS apps so that the operating system routes the link to your application.

### Registering a URI scheme on Android

Add an `<intent-filter>` to the activity that handles deep links (typically `MainActivity`) in `android/app/src/main/AndroidManifest.xml`:

Registering a deep-link intent-filter in AndroidManifest.xml

```xml
<activity
    android:name=".MainActivity"
    android:launchMode="singleTask"
    ...>

    <!-- Standard launch intent-filter -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <!-- Deep link intent-filter for magic links -->
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:scheme="myapp"
            android:host="example.com" />
    </intent-filter>

</activity>
```

Replace `myapp` and `example.com` with the scheme and host that match the **Magic Link Custom URL** configured on your Email Suspend Node.

|   |                                                                                                                                                                                                                                                  |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|   | Using `android:launchMode="singleTask"` ensures that clicking a magic link while the app is already running brings the existing instance to the foreground and delivers the URI through `onNewIntent` rather than creating a duplicate activity. |

### Registering a URI scheme on iOS

1. In Xcode, open your project's **Info** tab and add a new URL type under **URL Types**.

   * **Identifier**: Your bundle ID, for example `com.example.myapp`.

   * **URL Schemes**: The scheme portion of your deep link, for example `myapp`.

   Alternatively, add the entry directly to `ios/<YourApp>/Info.plist`:

   Registering a custom URI scheme in Info.plist

   ```xml
   <key>CFBundleURLTypes</key>
   <array>
       <dict>
           <key>CFBundleURLSchemes</key>
           <array>
               <string>myapp</string>
           </array>
           <key>CFBundleURLName</key>
           <string>com.example.myapp</string>
       </dict>
   </array>
   ```

2. Forward the URL to React Native's `Linking` module in `ios/<YourApp>/AppDelegate.swift`:

Forwarding deep-link URLs to React Native in AppDelegate.swift

```swift
import React

// Inside AppDelegate class
func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
    return RCTLinkingManager.application(app, open: url, options: options)
}

// Handle universal links and cold-start URLs
func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    return RCTLinkingManager.application(
        application,
        continue: userActivity,
        restorationHandler: restorationHandler
    )
}
```

## Creating the Journey client

Creating the Journey client

```typescript
import { createJourneyClient } from '@ping-identity/rn-journey';

const journeyClient = createJourneyClient({
  serverUrl: 'https://openam-forgerock-sdks.forgeblocks.com/am',
  realm: 'alpha',
  cookie: 'ch15fefc5407912',
  timeout: 30000,
});
```

The client instance is typically created once, at module scope or inside an initialization hook. The same client instance can handle both `start()` and `resume()` calls.

## Detecting the suspended journey

When the journey reaches an [Email Suspend node](https://docs.pingidentity.com/auth-node-ref/latest/email-suspend.html), the SDK receives a `ContinueNode` containing a `SuspendedTextOutputCallback`.

Use this to show a "check your email" message and wait for the magic link to be tapped:

Detecting a suspended journey node

```typescript
import type { JourneyNode } from '@ping-identity/rn-journey';

function handleNode(node: JourneyNode) {
  if (node.type === 'ContinueNode') {
    const suspended = node.callbacks?.find(
      (cb) => cb.type === 'SuspendedTextOutputCallback',
    );
    if (suspended) {
      // Journey is paused — show "check your email" UI
      showMessage(suspended.message);
      return;
    }
    // Otherwise render the callback inputs as normal
  }
}
```

## Handling the incoming deep link and resuming the journey

To handle the incoming URI and resume the journey, use React Native's `Linking` API with `actions.resume()` from the `useJourney` hook.

The hook handles node state, loading, and error internally:

Handling a deep-link URL and resuming the journey

```typescript
import React, { useEffect } from 'react';
import { Linking } from 'react-native';
import { useJourney } from '@ping-identity/rn-journey';

export function LoginScreen() {
  const [node, actions] = useJourney(journeyClient);

  useEffect(() => {
    // App launched cold from the link — the URL won't fire an event, so fetch it directly
    Linking.getInitialURL().then((url) => { if (url) actions.resume(url); });
    // App already running — the OS fires this event when the link is tapped
    const sub = Linking.addEventListener('url', ({ url }) => actions.resume(url));
    return () => sub.remove();
  }, []);

  // Render based on node ...
}
```

The `resume()` call accepts the complete URI string, including the `suspendedId` query parameter, and returns the next journey node, in the same manner as `start()` and `next()`.

## Handling errors

If `resume()` fails, for example, because the `suspendedId` has already been used or has expired, then `useJourney` sets `actions.error` to a `JourneyError` instance.

Read `actions.error` to surface the problem to the user:

Handling a resume error in the UI

```typescript
export function LoginScreen() {
  const [node, actions] = useJourney(journeyClient);

  if (actions.error) {
    return (
      <View>
        <Text>{actions.error.message}</Text>
        <Button title="Start over" onPress={() => actions.start('Login')} />
      </View>
    );
  }

  // ...
}
```

|   |                                                                                                                                                                                                                    |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|   | A `suspendedId` is single-use. Once the journey is resumed successfully, the identifier is invalidated.If the user visits the same link multiple times, `actions.error` is set with a `JOURNEY_RESUME_ERROR` code. |
