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

# Attribution

> Forward attribution data from AppsFlyer or another mobile attribution provider into PNLight so installs, Remote UI requests, and campaigns are linked correctly.

Attribution connects a user to the channel or campaign that brought them into the app.

## How attribution works in your app

Your supported attribution provider, such as AppsFlyer, Firebase, or Facebook, resolves attribution data. After the provider returns conversion data, send it to PNLight with the SDK.

PNLight uses the project attribution settings to decide which source has priority when more than one source can describe the same user.

## Recommended AppsFlyer flow

<Steps>
  <Step title="Start the campaign">
    Launch the campaign in AppsFlyer and use AppsFlyer links for user acquisition.
  </Step>

  <Step title="Receive the AppsFlyer ID">
    When a user opens the campaign link, AppsFlyer creates a unique ID for that user.
  </Step>

  <Step title="Request ATT permission">
    After the app is installed, request App Tracking Transparency permission when your app needs IDFA-based attribution.
  </Step>

  <Step title="Update IDFA in PNLight">
    If PNLight initialized before the ATT response, call `updateIdfa()` after authorization.
  </Step>

  <Step title="Initialize AppsFlyer">
    Initialize the AppsFlyer SDK after the ATT response.
  </Step>

  <Step title="Share the PNLight user ID">
    Optionally set the PNLight user ID in your trackers, such as `appsFlyer.setCustomerUserId` and `Apphud.updateUserID`, so the same user ID appears across tools.
  </Step>

  <Step title="Send conversion data to PNLight">
    Read the AppsFlyer conversion result and send it to PNLight with `addAttribution`.
  </Step>

  <Step title="Request Remote UI">
    On the first app launch, wait 4-5 seconds after AppsFlyer initialization before calling `getUIConfig` or showing `RemoteUiView`. When `attributionRequired` is `true`, the SDK also waits for attribution internally.
  </Step>
</Steps>

## Send attribution

<CodeGroup>
  ```swift Swift theme={null}
  await PNLightSDK.shared.addAttribution(
      provider: .appsFlyer,
      data: ["af_status": "Non-organic"],
      identifier: "appsFlyerCustomerId"
  )

  await PNLightSDK.shared.updateIdfa()
  ```

  ```tsx React Native theme={null}
  import { addAttribution, updateIdfa } from "@pnlight/sdk-react-native";

  await addAttribution(
    "appsFlyer",
    { af_status: "Non-organic" },
    "appsFlyerCustomerId"
  );

  await updateIdfa();
  ```
</CodeGroup>

## AppsFlyer examples

<CodeGroup>
  ```swift Swift theme={null}
  import AppTrackingTransparency
  import AppsFlyerLib
  import PNLightSDK
  import UIKit

  final class AppDelegate: NSObject, UIApplicationDelegate, AppsFlyerLibDelegate {
      func application(
          _ application: UIApplication,
          didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
      ) -> Bool {
          AppsFlyerLib.shared().appsFlyerDevKey = "your-appsflyer-dev-key"
          AppsFlyerLib.shared().appleAppID = "your-ios-app-id"
          AppsFlyerLib.shared().delegate = self

          Task {
              await PNLightSDK.shared.initialize(apiKey: "pnlight_sdk_token")
              await ATTrackingManager.requestTrackingAuthorization()
              await PNLightSDK.shared.updateIdfa()
              AppsFlyerLib.shared().customerUserID = PNLightSDK.shared.getUserId()
              AppsFlyerLib.shared().start()
          }

          return true
      }

      func onConversionDataSuccess(_ installData: [AnyHashable: Any]) {
          let data = Dictionary(uniqueKeysWithValues: installData.compactMap { key, value in
              guard let key = key as? String else { return nil }
              return (key, value)
          })

          Task {
              await PNLightSDK.shared.addAttribution(
                  provider: .appsFlyer,
                  data: data,
                  identifier: AppsFlyerLib.shared().getAppsFlyerUID()
              )
          }
      }

      func onConversionDataFail(_ error: Error) {
          print("AppsFlyer conversion data error:", error)
      }
  }
  ```

  ```tsx React Native theme={null}
  import appsFlyer from "react-native-appsflyer";
  import { requestTrackingPermission } from "react-native-tracking-transparency";
  import {
    addAttribution,
    getUserId,
    initialize,
    updateIdfa,
  } from "@pnlight/sdk-react-native";

  const getAppsFlyerUID = () =>
    new Promise<string | null>((resolve) => {
      appsFlyer.getAppsFlyerUID((error, uid) => {
        if (error) {
          console.error("AppsFlyer UID error", error);
          resolve(null);
          return;
        }

        resolve(uid);
      });
    });

  appsFlyer.onInstallConversionData(async (conversionData) => {
    const appsFlyerId = await getAppsFlyerUID();

    await addAttribution(
      "appsFlyer",
      conversionData?.data ?? conversionData,
      appsFlyerId
    );
  });

  await initialize("pnlight_sdk_token");
  await requestTrackingPermission();
  await updateIdfa();

  appsFlyer.initSdk({
    devKey: "your-appsflyer-dev-key",
    appId: "your-ios-app-id",
    isDebug: false,
    onInstallConversionDataListener: true,
  }, async () => {
    const pnlightUserId = await getUserId();
    appsFlyer.setCustomerUserId(pnlightUserId);
  });
  ```

  ```dart Flutter theme={null}
  import 'package:app_tracking_transparency/app_tracking_transparency.dart';
  import 'package:appsflyer_sdk/appsflyer_sdk.dart';
  import 'package:pnlight_sdk/pnlight_sdk.dart';

  final appsFlyerOptions = AppsFlyerOptions(
    afDevKey: 'your-appsflyer-dev-key',
    appId: 'your-ios-app-id',
    showDebug: false,
  );

  final appsFlyerSdk = AppsflyerSdk(appsFlyerOptions);

  await PNLightSDK.initialize('pnlight_sdk_token');
  await AppTrackingTransparency.requestTrackingAuthorization();
  await PNLightSDK.updateIdfa();

  appsFlyerSdk.onInstallConversionData((conversionData) async {
    final appsFlyerId = await appsFlyerSdk.getAppsFlyerUID();
    final payload = conversionData['payload'] ?? conversionData;

    await PNLightSDK.addAttribution(
      provider: 'appsFlyer',
      data: Map<String, dynamic>.from(payload as Map),
      identifier: appsFlyerId,
    );
  });

  await appsFlyerSdk.initSdk(
    registerConversionDataCallback: true,
    registerOnAppOpenAttributionCallback: false,
    registerOnDeepLinkingCallback: false,
  );
  ```
</CodeGroup>

<Tip>
  Most of the waiting time before a first-launch Remote UI request comes from AppsFlyer attribution. Wait 4-5 seconds after AppsFlyer initialization when attribution is required.
</Tip>

## Supported provider names

| SDK          | Provider values                           |
| ------------ | ----------------------------------------- |
| Swift        | `.appsFlyer`, `.firebase`, `.facebook`    |
| React Native | `"appsFlyer"`, `"firebase"`, `"facebook"` |
| Flutter      | `"appsFlyer"`, `"firebase"`, `"facebook"` |

## Dashboard settings

Open **Settings** and configure the attribution priority:

* Put the preferred source first.
* Keep at least one source selected.
* Set the wait window for preferred attribution.

<Note>
  A longer wait window can improve attribution completeness, but it can also delay early decisions that depend on attribution.
</Note>
