> For the complete documentation index, see [llms.txt](https://docs.amply.tools/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.amply.tools/developer-guide/handling-deeplinks.md).

# Handling deeplinks

When a campaign fires, Amply tells your app where to go — a screen, a sheet, a URL — by sending a **deeplink**. Your app decides what that URL means and whether it was handled.

A PM should read this as: "when a campaign targets this user, the SDK hands the app a URL with some metadata, and the app navigates accordingly. The developer wires that URL into their app's navigation."

**Use this when** you run campaigns that route users to screens, external URLs, or in-app surfaces. **Don't use this when** a campaign only needs to record an event or set a property — no deeplink is involved in those cases.

## What the SDK delivers

When a campaign matches the current user and fires, the SDK invokes your registered listener with:

* **`url`** — the target URL chosen by the campaign (`yourapp://promo/annual-upgrade`, `https://example.com/help`, or a custom scheme you own)
* **`info`** — a map of metadata the campaign attached to this deeplink (campaign ID, variant, anything the PM configured)
* **`consumed`** (React Native only) — whether another listener in the chain already handled this URL

Your listener returns `true` if it handled the URL, `false` otherwise. Returning `true` lets the SDK know the deeplink was acted on.

## Registering a listener

{% tabs %}
{% tab title="iOS (Swift)" %}

```swift
import AmplySDK

class AppDeepLinkHandler: DeepLinkListener {
    func onDeepLink(url: String, info: [String: Any]) -> Bool {
        print("Amply deeplink: \(url) info: \(info)")

        if url.hasPrefix("yourapp://promo/") {
            // Navigate to promo screen
            Router.shared.showPromo(url: url)
            return true
        }

        // Let another handler deal with it
        return false
    }
}

// Somewhere during app startup, after SDK init:
let handler = AppDeepLinkHandler()
amply.registerDeepLinkListener(listener: handler)
```

Hold a strong reference to your listener from your own side so it stays alive for the lifetime of the app.
{% endtab %}

{% tab title="Android (Kotlin)" %}

```kotlin
import tools.amply.sdk.actions.DeepLinkListener

class AppDeepLinkHandler : DeepLinkListener {
    override fun onDeepLink(url: String, info: Map<String, Any>): Boolean {
        Log.d("Amply", "deeplink: $url info: $info")

        if (url.startsWith("yourapp://promo/")) {
            Router.showPromo(url)
            return true
        }

        return false
    }
}

// During Application.onCreate(), after amply init:
amply.registerDeepLinkListener(AppDeepLinkHandler())
```

{% endtab %}

{% tab title="React Native (TS)" %}

```ts
import Amply from '@amplytools/react-native-amply-sdk';
import { useEffect } from 'react';

export function useAmplyDeepLinks(onPromo: (url: string) => void) {
  useEffect(() => {
    let unsubscribe: (() => void) | undefined;
    let unmounted = false;

    Amply.addDeepLinkListener(event => {
      console.log('Amply deeplink:', event.url, event.info, 'consumed=', event.consumed);

      if (event.url.startsWith('yourapp://promo/')) {
        onPromo(event.url);
      }
    })
      .then(unsub => {
        if (unmounted) unsub();
        else unsubscribe = unsub;
      });

    return () => {
      unmounted = true;
      unsubscribe?.();
    };
  }, [onPromo]);
}
```

`addDeepLinkListener` returns a `Promise<() => void>`. Await it to get the unsubscribe function, and call it on unmount.
{% endtab %}
{% endtabs %}

## Return value (iOS + Android)

`onDeepLink` returns `Boolean`:

* `true` — your listener handled the URL. The SDK treats it as consumed.
* `false` — your listener did not handle this URL. The SDK may hand it to another registered listener, or fall through.

On React Native, the listener does not return a value. Instead, the SDK hands you a `consumed` flag that tells you whether an earlier subscriber already handled this URL.

## Multiple listeners

All three SDKs support multiple listeners. Each listener sees every deeplink. The native iOS and Android SDKs consider the deeplink "consumed" if any listener returns `true`. On React Native, listeners receive a `consumed` flag indicating whether another listener already handled this URL.

In practice, most apps register one listener at app startup and route by URL prefix.

## When the app should wait for the outcome

A deeplink listener is fire-and-forget: the SDK hands you the URL and the campaign is done. Some actions instead need the user's flow to **wait** for them — a rewarded ad before an export, a survey before a cancel. Those are **gates**: you register them separately and the gated call site awaits the outcome. See [Gating actions](/developer-guide/gating-actions.md). Everything you don't register as a gate stays an ordinary deeplink, handled here.

## React Native: unsubscribing

`addDeepLinkListener` is async and returns an unsubscribe function. Always unsubscribe when the listener's owning component unmounts — otherwise closed-over state will leak:

```ts
useEffect(() => {
  let unsubscribe: (() => void) | undefined;
  Amply.addDeepLinkListener(handler).then(unsub => { unsubscribe = unsub; });
  return () => { unsubscribe?.(); };
}, []);
```

For a full teardown (e.g., sign-out), you can also call `Amply.removeAllListeners()` which unsubscribes every deep link listener the SDK tracks.

## URL schemes

`yourapp://` throughout these docs is a placeholder for **a scheme your app owns and registers** (in `Info.plist` on iOS, your intent filter on Android) — Amply doesn't reserve or ship a deeplink scheme. A campaign simply carries whatever URL string you configure: a custom scheme like `yourapp://promo/123`, or an external `https://` link. Treat the `url` field as an opaque string; parse it however your app normally parses URLs.

External `https://` URLs coming from campaigns are typically opened by native code through the system's URL handling. The deeplink listener still receives them so the app can log or suppress them.

## Testing

You can simulate a campaign-style deeplink from the terminal:

```bash
# iOS Simulator
xcrun simctl openurl booted "yourapp://promo/test"

# Android device or emulator
adb shell am start -a android.intent.action.VIEW -d "yourapp://promo/test" <your.package.name>
```

Note: these open the OS-level URL. Campaign-driven deeplinks are triggered by matching a real campaign; see [Testing your integration](/developer-guide/testing-your-integration.md) for how to verify end-to-end.

## Related

* [Gating actions](/developer-guide/gating-actions.md) — for actions the user's flow must wait on (rewarded ad, survey)
* [Showing custom popups](/developer-guide/showing-custom-popups.md) — when the campaign wants the app to render a popup
* [Handling callbacks](/developer-guide/handling-callbacks.md) — other SDK events (session, config, init)
* [Concepts — Scenarios and campaigns](/concepts/scenarios-and-campaigns.md) — how the dashboard decides what deeplink to send
* [iOS SDK reference](/reference/sdk-ios.md) — exact signatures
* [Android SDK reference](/reference/sdk-android.md) — exact signatures
* [React Native SDK reference](/reference/sdk-react-native.md) — exact signatures


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.amply.tools/developer-guide/handling-deeplinks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
