Skip to main content
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.
1

Start the campaign

Launch the campaign in AppsFlyer and use AppsFlyer links for user acquisition.
2

Receive the AppsFlyer ID

When a user opens the campaign link, AppsFlyer creates a unique ID for that user.
3

Request ATT permission

After the app is installed, request App Tracking Transparency permission when your app needs IDFA-based attribution.
4

Update IDFA in PNLight

If PNLight initialized before the ATT response, call updateIdfa() after authorization.
5

Initialize AppsFlyer

Initialize the AppsFlyer SDK after the ATT response.
6

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.
7

Send conversion data to PNLight

Read the AppsFlyer conversion result and send it to PNLight with addAttribution.
8

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.

Send attribution

await PNLightSDK.shared.addAttribution(
    provider: .appsFlyer,
    data: ["af_status": "Non-organic"],
    identifier: "appsFlyerCustomerId"
)

await PNLightSDK.shared.updateIdfa()
import { addAttribution, updateIdfa } from "@pnlight/sdk-react-native";

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

await updateIdfa();

AppsFlyer examples

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)
    }
}
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);
});
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,
);
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.

Supported provider names

SDKProvider 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.
A longer wait window can improve attribution completeness, but it can also delay early decisions that depend on attribution.