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

# Custom URL Scheme

> Register and handle the Userpilot custom URL scheme on Android to preview draft experiences and route push notification taps.

The Userpilot Android SDK supports a custom URL scheme that lets you preview experiences on a real device before publishing them, and that carries push notification taps back into your app.

Setting it up is two steps: add an `<intent-filter>` to your `AndroidManifest.xml` so Android routes matching links to your Activity, then forward the incoming `Intent` to the Userpilot SDK. The SDK does all the validation, so you hand it every Intent your Activity receives and act on the result.

***

## Register the custom URL scheme

Add an intent filter with the custom scheme inside the desired `<activity>` element. Register it on the Activity that hosts your app, which is usually the one already marked with `<action android:name="android.intent.action.MAIN" />`.

Replace `APP_TOKEN` with your app's Userpilot token, which you can obtain from your [Environments Page](https://run.userpilot.io/environment). For example, if your token is `NX-12345678`, your scheme value is `userpilot-nx-12345678`.

Set `android:launchMode="singleTop"` so an incoming link is delivered to the running Activity through `onNewIntent` instead of creating a second instance of it.

```xml theme={null}
<activity
    android:name="..."
    android:exported="true"
    android:launchMode="singleTop">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <data android:scheme="userpilot-APP_TOKEN" android:host="sdk" />
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
    </intent-filter>
</activity>
```

Keep `android:host="sdk"` in this filter. Userpilot validates that the incoming URL uses the `sdk` host before handling preview or push-notification links.

***

Use the token exactly as passed to the SDK, in lowercase. Keep the `STG-` prefix
for staging: `STG-NX-12345678` uses `userpilot-stg-nx-12345678`, while
`NX-12345678` uses `userpilot-nx-12345678`.

## Handle the custom URL scheme

Forward the Intent to `userpilot.onNewIntent(intent)`. It returns `true` if the link was a Userpilot link and the SDK handled it, and `false` if your app should handle the Intent itself.

An Intent reaches your Activity through either `onCreate` (a cold launch from a link) or `onNewIntent` (a link that arrives while your app is already running). Override both and forward from each, otherwise links only work in one of the two cases.

```kotlin theme={null}
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleLinkIntent(intent)
}

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    handleLinkIntent(intent)
}

private fun handleLinkIntent(intent: Intent?) {
    val userpilotHandled = userpilot.onNewIntent(intent)
    if (userpilotHandled) return

    // Not a Userpilot link, so the application should handle it
}
```

<Warning>
  **`onNewIntent` takes a single argument**

  The signature is `onNewIntent(intent: Intent?): Boolean`. Earlier guides showed a two-argument form that passed the Activity as well; that overload does not exist. The other overload, `onNewIntent(intent: Map<String, Any?>)`, is for forwarding a push notification payload you already parsed yourself, not for deep links.
</Warning>

***

## Supported links

The SDK only claims an Intent when the scheme is one of the accepted values, the host is `sdk`, and the action is `VIEW` or `MAIN`. Every other Intent is passed back to your app.

| **Link** | **Purpose** |
| - | - |
| `userpilot-{token}://sdk/experience_preview/{experience_id}` | Previews a draft Flow or Survey. This is the link behind the Builder's QR code. |
| `userpilot-{token}://sdk/notification` | Routes a push notification tap to the SDK. Query parameters carry the notification payload. |

<Note>
  You don't build the `sdk/notification` link yourself. The SDK posts each notification with a matching `PendingIntent`, so a tap arrives at your Activity as a normal deep link and is covered by the same `onNewIntent` call. See [Push Notifications](./push-notifications) for the delivery setup.
</Note>

***

## Preview an experience with a QR code

Userpilot can render a draft experience on your device before you publish it, so you can check layout, copy, and behavior on a real screen. Preview is supported for **Flows** and **Surveys**.

The preview is delivered over the custom URL scheme. It works once your app is installed on the device and the scheme is registered and handled as described above.

<Steps>
  <Step title="Open the experience">
    Open a Flow or Survey in the Experience Builder and enter **Edit Mode**.
  </Step>

  <Step title="Start the preview">
    Select **Preview** in the top-right corner of the builder. A popup with a **QR code** appears.
  </Step>

  <Step title="Scan the QR code">
    Scan the code with your device camera. Your app opens and the experience appears.
  </Step>
</Steps>

Points to keep in mind:

* You don't need to apply or save your changes before previewing them.
* The experience doesn't need to be live.
* A preview bypasses the experience's targeting and frequency settings, so it appears even when the current user wouldn't normally qualify for it.
* Once you close the experience, the app resumes normal behavior.

***

## Test push notifications

You can send a test push to a specific user from the Userpilot dashboard and verify delivery and tap handling on a real device without sending anything to your wider audience. The notification doesn't need to be live, so you can test it while it's still a draft.

* Test pushes work in both the staging and production environments.
* Test pushes are **not** counted as real sends, and they're excluded from analytics.
* Test pushes bypass the notification's targeting and frequency settings.

For the dashboard steps, see [Mobile push notifications](/in-app-engagement/mobile-content/mobile-push-notifications).

Because a notification tap arrives as a deep link on Android, a test push also exercises your `onNewIntent` handling. If the notification appears but tapping it does nothing, the scheme registration is the first thing to check.

***

## Test a deep link from the command line

Open a link with `adb` to confirm your scheme is registered and your handler runs, without going through the Builder:

```bash theme={null}
adb shell am start -a android.intent.action.VIEW \
  -d "userpilot-nx-12345678://sdk/experience_preview/12345"
```

Replace the scheme with your own token and `12345` with a real experience ID. Enable SDK logging during setup to see the SDK report which link it received and what it did with it.

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Nothing happens when I scan the QR code">
    Confirm the scheme in `AndroidManifest.xml` matches your token exactly, lowercased. A mismatched scheme means Android never routes the link to your Activity, so your handler is not called at all. Verify the registration with the `adb shell am start` command above: if `adb` reports no matching Activity, the intent filter is the problem.
  </Accordion>

  <Accordion title="The app opens but the experience doesn't appear">
    Android routed the link, so registration is correct and the problem is in the handling step. Check that you forward the Intent from **both** `onCreate` and `onNewIntent`. A cold launch only passes through `onCreate`, so handling just `onNewIntent` silently drops links that start the app. Also confirm the SDK is set up with the same token that generated the QR code.
  </Accordion>

  <Accordion title="Tapping a push notification does nothing">
    Notification taps travel over this same URL scheme, so they need the intent filter and the `onNewIntent` forwarding described above. Confirm the filter host is exactly `sdk`.
  </Accordion>

  <Accordion title="A link opens a second copy of my Activity">
    Set `android:launchMode="singleTop"` on the Activity. Without it, Android may create a new instance for the incoming link rather than delivering it to the running one through `onNewIntent`.
  </Accordion>

  <Accordion title="Preview works in production but not in staging">
    Each environment has its own token, and the scheme follows the token. Confirm the staging build registers the scheme for the token it initializes the SDK with.
  </Accordion>
</AccordionGroup>

<Frame>
  [**For any questions or concerns please reach out to support@userpilot.com**](mailto:support@userpilot.com)
</Frame>
