# Deep link attribution

> Learn how to use deep link attribution to track conversions events with Dub.

import EnableConversionTracking from "/snippets/enable-conversion-tracking.mdx";
import GeneratePublishableKeyStep from "/snippets/steps/generate-publishable-key.mdx";
import AllowlistDomainsStep from "/snippets/steps/allowlist-domains.mdx";
import InstallIosSdkStep from "/snippets/steps/install-ios-sdk.mdx";
import InstallReactNativeSdkStep from "/snippets/steps/install-react-native-sdk.mdx";
import InitializeIosSdkStep from "/snippets/steps/initialize-ios-sdk.mdx";
import InitializeReactNativeSdkStep from "/snippets/steps/initialize-react-native-sdk.mdx";
import ViewConversions from "/snippets/view-conversions.mdx";

<Note>
  Deep link attribution requires a [Business
  plan](https://dub.co/pricing/partners) subscription or higher.
</Note>

Dub's powerful [attribution platform](/docs/concepts/attribution) lets you understand how well your deep links are translating to actual users and revenue dollars inside your app.

<Frame>
  <img
    src="https://assets.dub.co/blog/introducing-dub-conversions.webp"
    alt="Conversion analytics"
  />
</Frame>

<Note>
  This feature is currently only available for iOS (Swift) and React Native.
  Android (Kotlin) support is coming soon. If you'd like early access, please
  [contact us](https://dub.co/contact/support).
</Note>

## Prerequisites

<EnableConversionTracking />

Then, you'll need generate a [publishable key](/docs/api-reference/authentication#publishable-keys) from your Dub workspace to track conversions on the client-side.

To do that, navigate to your [workspace's Tracking settings page](https://app.dub.co/settings/tracking) and generate a new publishable key under the **Publishable Key** section.

<Frame>
  <img
    src="/images/conversions/publishable-key.png"
    alt="Enabling conversion tracking for a workspace"
  />
</Frame>

Once these are set up, we can start tracking conversion events for your deep links.

## Step 1: Install the client-side Mobile SDK

<Tabs>
<Tab title="React Native">

Install the [Dub React Native SDK](/docs/sdks/client-side-mobile/installation-guides/react-native) and initialize it with your publishable key and short link domain.

<Steps titleSize="h3">

<InstallReactNativeSdkStep />

<InitializeReactNativeSdkStep />

</Steps>
</Tab>
<Tab title="iOS">

Install the [Dub iOS SDK](/docs/sdks/client-side-mobile/installation-guides/swift) and initialize it with your publishable key and short link domain.

<Steps titleSize="h3">

<InstallIosSdkStep />

<InitializeIosSdkStep />

</Steps>
</Tab>
</Tabs>

## Step 2: Track deep link open events

Once the SDK has been initialized, you can start tracking deep link and deferred deep link events.

Call `trackOpen` on the `dub` instance to track deep link and deferred deep link open events. The `trackOpen` function should be called once without a `deepLink` parameter on first launch, and then again with the `deepLink` parameter whenever the app is opened from a deep link.

<CodeGroup>

```typescript React Native expandable
import { useState, useEffect, useRef } from "react";
import { Linking } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import dub from "@dub/react-native";

export default function App() {
  useEffect(() => {
    dub.init({
      publishableKey: "<DUB_PUBLISHABLE_KEY>",
      domain: "<DUB_DOMAIN>",
    });

    // Check if this is first launch
    const isFirstLaunch = await AsyncStorage.getItem("is_first_launch");

    if (isFirstLaunch === null) {
      await handleFirstLaunch();
      await AsyncStorage.setItem("is_first_launch", "false");
    } else {
      // Handle initial deep link url (Android only)
      const url = await Linking.getInitialURL();

      if (url) {
        await handleDeepLink(url);
      }
    }

    const linkingListener = Linking.addEventListener("url", (event) => {
      handleDeepLink(event.url);
    });

    return () => {
      linkingListener.remove();
    };
  }, []);

  const handleFirstLaunch = async (
    deepLinkUrl?: string | null | undefined,
  ): Promise<void> => {
    try {
      const response = await dub.trackOpen(deepLinkUrl);

      const destinationURL = response.link?.url;
      // Navigate to the destination URL
    } catch (error) {
      // Handle error
    }
  };

  // Return your app...
}
```

```swift iOS (SwiftUI) expandable
// ContentView.swift
import SwiftUI
import Dub

struct ContentView: View {

    @Environment(\.dub) var dub: Dub

    @AppStorage("is_first_launch") private var isFirstLaunch = true

    var body: some View {
        NavigationStack {
            VStack {
                // Your app content
            }
            .onOpenURL { url in
                trackOpen(deepLink: url)
            }
            .onAppear {
                if isFirstLaunch {
                    trackOpen()
                    isFirstLaunch = false
                }
            }
        }
    }

    private func trackOpen(deepLink: URL? = nil) {
        Task {
            do {
                let response = try await dub.trackOpen(deepLink: deepLink)

                // Obtain the destination URL from the response
                guard let url = response.link?.url else {
                    return
                }

                // Navigate to the destination URL
            } catch let error as DubError {
                print(error.localizedDescription)
            }
        }
    }
}
```

```swift iOS (UIKit) expandable
import UIKit
import Dub

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    private let dubPublishableKey = "<DUB_PUBLISHABLE_KEY>"
    private let dubDomain = "<DUB_DOMAIN>"

    private var isFirstLaunch: Bool {
        get {
            UserDefaults.standard.object(forKey: "is_first_launch") as? Bool ?? true
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "is_first_launch")
        }
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Dub.setup(publishableKey: dubPublishableKey, domain: dubDomain)

        // Track first launch
        if isFirstLaunch {
            trackOpen()
            isFirstLaunch = false
        }

        // Override point for customization after application launch.
        return true
    }

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        handleDeepLink(url: url)
        return true
    }

    func handleDeepLink(url: URL) {
        trackOpen(deepLink: url)
    }

    private func trackOpen(deepLink: URL? = nil) {
        // Call the tracking endpoint with the full deep link URL
        Task {
            do {
                let response = try await Dub.shared.trackOpen(deepLink: deepLink)

                print(response)

                // Navigate to final link via link.url
                guard let destinationUrl = response.link?.url else {
                    return
                }

                // Navigate to the destination URL
            } catch let error as DubError {
                print(error.localizedDescription)
            }
        }
    }
}
```

</CodeGroup>

If the deep link was successfully resolved and correlated to the original click, the `response` object will contain the destination URL, which you can use to navigate the user to the appropriate screen.

It will also contain the `clickId`, which the `dub` instance will persist internally.

## Step 3: Track conversion events

You may track conversion events directly in your app with the `trackLead` and `trackSale` methods.

<CodeGroup>

```typescript React Native expandable
import dub from "@dub/react-native";

function trackLead(user: User) {
  try {
    await dub.trackLead({
      eventName: "User Sign Up",
      customerExternalId: user.id,
      customerName: user.name,
      customerEmail: user.email,
    });
  } catch (error) {
    // Handle sale tracking error
  }
}

function trackSale(user: User, product: Product) {
  try {
    await dub.trackSale({
      customerExternalId: user.id,
      amount: product.price.amount,
      currency: "usd",
      eventName: "Purchase",
    });
  } catch (error) {
    // Handle sale tracking error
  }
}
```

```swift iOS (SwiftUI) expandable
// ContentView.swift
import SwiftUI
import Dub

struct ContentView: View {

    @Environment(\.dub) var dub: Dub

    var body: some View {
        // ... your app content ...
    }

    private func trackLead(customerExternalId: String, name: String, email: String) {
        Task {
            do {
                let response = try await dub.trackLead(
                    eventName: "Sign Up",
                    customerExternalId: customerExternalId,
                    customerName: name,
                    customerEmail: email
                )

                print(response)
            } catch let error as DubError {
                print(error.localizedDescription)
            }
        }
    }

    private func trackSale(
        customerExternalId: String,
        amount: Int,
        currency: String = "usd",
        eventName: String? = "Purchase",
        customerName: String? = nil,
        customerEmail: String? = nil,
        customerAvatar: String? = nil
    ) {
        Task {
            do {
                let response = try await dub.trackSale(
                    customerExternalId: customerExternalId,
                    amount: amount,
                    currency: currency,
                    eventName: eventName,
                    customerName: customerName,
                    customerEmail: customerEmail,
                    customerAvatar: customerAvatar
                )

                print(response)
            } catch let error as DubError {
                print(error.localizedDescription)
            }
        }
    }
}
```

```swift iOS (UIKit) expandable
// ViewController.swift
import UIKit
import Dub

class ViewController: UIViewController {
    // View controller lifecycle

    private func trackLead(customerExternalId: String, name: String, email: String) {
        Task {
            do {
                let response = try await dub.trackLead(customerExternalId: customerExternalId, name: name, email: email)
            } catch let error as DubError {
                print(error.localizedDescription)
            }
        }
    }

    private func trackSale(customerExternalId: String, amount: Int, currency: String = "usd", eventName: String? = "Purchase", customerName: String? = nil, customerEmail: String? = nil, customerAvatar: String? = nil) {
        Task {
            do {
                let response = try await dub.trackSale(customerExternalId: customerExternalId, amount: amount, currency: currency, eventName: eventName, customerName: customerName, customerEmail: customerEmail, customerAvatar: customerAvatar)
            } catch let error as DubError {
                print(error.localizedDescription)
            }
        }
    }
}
```

</CodeGroup>

Alternatively, you can [track conversion events server-side](/docs/quickstart/server) by sending the `clickId` resolved from the deep link to your backend and then calling off to either:

- [`POST /track/lead`](/docs/api-reference/track/lead)
- [`POST /track/sale`](/docs/api-reference/track/sale)

## Step 4: View your conversions

Once you've enabled conversion tracking for your links, all your tracked conversions will show up on your [Analytics dashboard](https://app.dub.co/analytics). We provide 3 different views to help you understand your conversions:

<ViewConversions />
