Skip to content

React Native SDK ​

Linkrunner MMP SDK for React Native: install attribution, deferred deep linking, in-app events and revenue tracking.

Prefer to let your AI coding agent do this? Install the Linkrunner skill and Claude Code, Cursor, GitHub Copilot, or Windsurf will wire up the SDK and deep links for you:

bash
npx @linkrunner/skills add react-native

Then ask your agent to "integrate Linkrunner". See Linkrunner Agent Skills.

Installation ​

Step 1: Install the Package ​

bash
# Using npm
npm install rn-linkrunner

# Using yarn
yarn add rn-linkrunner

Step 2: iOS Configuration ​

If you're developing for iOS, follow these additional steps:

  1. Install pods for the package:
bash
cd ios && pod install
  1. Add the following code to your info.plist file:
xml
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads and improve your app experience.</string>

Step 3: SKAdNetwork Configuration ​

To enable SKAdNetwork postback copies to be sent to Linkrunner, add the following keys to your Info.plist file:

xml
<key>NSAdvertisingAttributionReportEndpoint</key>
<string>https://linkrunner-skan.com</string>
<key>AttributionCopyEndpoint</key>
<string>https://linkrunner-skan.com</string>

For complete SKAdNetwork integration details, see the SKAdNetwork Integration Guide.

Step 4: Android Backup Configuration ​

For Android apps, the SDK provides backup rules to exclude Shared Preferences data from backup. This prevents the retention of the Linkrunner install ID during reinstallation, ensuring accurate detection of new installs and re-installs.

For detailed backup configuration instructions, please refer to the Android SDK Backup Configuration.

Step 5: Encrypted SharedPreferences (Android) ​

SDK Version Requirement: Starting from rn-linkrunner v2.10.1, the SDK automatically encrypts the credentials it stores in Android SharedPreferences (such as the install ID and other persisted SDK state). No additional configuration is required. Upgrade to v2.10.1 or above to get this behavior by default.

On Android, values written by the SDK are encrypted at rest, with a hardware-protected key generated on the device and stored in the Android Keystore. The key never leaves the device and is not bundled with the SDK.

If you are upgrading from an earlier version, the SDK will transparently migrate any existing plaintext entries to the encrypted store on the next read after the upgrade. No code changes are needed on your side.

Expo Support ​

If you are using Expo, follow the above steps to install the required packages. After this, you will need to use development builds since the Linkrunner SDK relies on native libraries. Follow the Expo Development Builds Documentation to get started.

Google Integrated Conversion Measurement (Optional) ​

Prefer to let your AI coding agent do this? The React Native skill already handles ICM. It adds the pod and wires setConsent:

bash
npx @linkrunner/skills add react-native

See Linkrunner Agent Skills.

Integrated Conversion Measurement (ICM) recovers Google App Campaign installs on iOS that Google cannot attribute because there is no click identifier and no IDFA to match on. Google's On-Device Measurement (ODM) SDK turns the click context into an encrypted signal that never leaves the device, and Linkrunner sends it with the install. See Google ICM for how it works.

Set this up if you run Google App Campaigns for your iOS app. Requires rn-linkrunner 3.1.0 or later.

Google keeps ODM inactive for users in the European Economic Area, the United Kingdom, and Switzerland, so ICM recovers nothing for that traffic. Elsewhere, Google reports improved coverage for iOS 14+ users.

ICM also needs an iOS link ID configured in your Google Ads integration. Google has nowhere to send the conversion without one. See Prerequisites.

Add Google's On-Device Measurement SDK (iOS)

Already using the Firebase iOS SDK 11.14.0 or later? The FirebaseAnalytics pod brings this SDK in for you. Skip this step.

rn-linkrunner does not bundle this SDK, so apps that skip ICM carry none of its weight. Add it inside your app target in ios/Podfile:

ruby
target 'YourApp' do
  # ...your existing config

  pod 'GoogleAdsOnDeviceConversion'
end

Then install the pods:

bash
cd ios && pod install

CocoaPods adds the -ObjC and -lc++ linker flags for you, so there are no Build Settings to change.

Report consent

Set the values with setConsent before you call init, and again whenever the user changes their choice:

javascript
import linkrunner from "rn-linkrunner";

linkrunner.setConsent({
    isEEA: "granted",
    hasConsentForDataUsage: "granted",
    hasConsentForAdsPersonalization: "denied",
});

await linkrunner.init("YOUR_PROJECT_TOKEN");

Each signal takes "granted", "denied", or "unknown". Anything omitted or left "unknown" is dropped from the payload rather than reported as a denial, so Linkrunner never reports a choice your user did not make.

ParameterMeaning
isEEAEuropean regulations apply to this user (the EEA, the UK, or Switzerland)
hasConsentForDataUsageThe user agreed to their data being sent to Google for advertising
hasConsentForAdsPersonalizationThe user agreed to their data being used to personalize ads

Google treats these as required whenever their value is known. hasConsentForDataUsage decides whether Google may use the conversion at all, hasConsentForAdsPersonalization decides whether it may feed audiences and remarketing, and isEEA tells Google which rules apply. Set them from your app's real consent state rather than hardcoding them. For users outside the EEA, the UK, and Switzerland, report isEEA as denied and leave the other two unset. See Send Consent.

setConsent works on both iOS and Android. Android has no ODM SDK to add, but its installs reach Google through the App Conversion API, which reads the same signals.

That is the whole integration. The native SDK fetches the value at initialization and there is no other API to call. Attribution comes back through getAttributionData as usual.

Consent is stored between launches. Call setConsent again whenever the user's consent state changes, otherwise the previous value keeps being sent after the user has withdrawn it.

Verifying your setup ​

Initialize with debug mode on and look for this line in the Xcode console:

Linkrunner: odm_available=true odm_fetch_result=success odm_fetch_latency_ms=124

odm_available=false with odm_fetch_result=unavailable means Google's SDK is not linked. Check that pod install picked up GoogleAdsOnDeviceConversion.

Initialization (Required) ​

To initialize the Linkrunner SDK, add this code to your App.tsx component:

You can find your project token here.

Note: The initialization method doesn't return any value. To get attribution data and deeplink information, use the getAttributionData method.

javascript
import linkrunner from "rn-linkrunner";

// Inside your React component
useEffect(() => {
    init();
}, []); // Empty dependency array ensures it runs only once

const init = async () => {
    await linkrunner.init(
        "YOUR_PROJECT_TOKEN",
        "YOUR_SECRET_KEY", // Optional: Required for SDK signing
        "YOUR_KEY_ID", // Optional: Required for SDK signing
        false, // Optional: Set to true to disable IDFA collection for iOS devices (defaults to false)
        true // Optional: Enable debug mode for development (defaults to false)
    );
    console.log("Linkrunner initialized");
};

SDK Signing Parameters (Optional) ​

For enhanced security, the LinkRunner SDK requires the following signing parameters during initialization:

  • secretKey: A unique secret key used for request signing and authentication
  • keyId: A unique identifier for the key pair used in the signing process

You can find your project token, secret key, and key ID here.

Setting the Customer User ID ​

Use setCustomerUserId to attach your own user identifier to the device right after init. Once set, the identifier is stored securely on-device and automatically included in every event you track, so you never have to pass it on each trackEvent call.

Call it as early as the user's ID is available. This guarantees every event carries a user_id from the very first event, and is especially useful for existing users who were already onboarded before this feature shipped.

Available from React Native SDK v2.11.0.

Best practice: set the Customer User ID as early as possible. The user_id is only attached to events tracked after it's set, and is not applied retroactively. Use a stable, unique identifier from your own system (for example your internal user ID or a UUID) rather than an email address or other PII.

javascript
const setCustomerUserId = async () => {
    try {
        await linkrunner.setCustomerUserId("f47ac10b-58cc-4372-a567-0e02b2c3d479"); // Your unique customer user ID (e.g. a UUID)
        console.log("Customer user id set");
    } catch (error) {
        console.error("Error setting customer user id:", error);
    }
};

The identifier is stored securely on-device and persists across app restarts. Calling setCustomerUserId again with a different identifier updates the stored value; passing the same identifier is a no-op. signup(), and setUserData() called with an id, also update it.

User Identification (Required) ​

Call the signup method as soon as the user is identified, whether through signup or login. This is the moment Linkrunner ties the install (and any future events) to a user identifier.

It is strongly recommended to use the integrated platform's identify function to set a persistent user_id once it becomes available (typically after signup or login).

If the platform's identifier function is not called, you must provide a user identifier for Mixpanel, PostHog, and Amplitude integration.

  • mixpanel_distinct_id for Mixpanel
  • posthog_distinct_id for PostHog
  • amplitude_device_id for Amplitude
javascript
const onSignup = async () => {
    try {
        await linkrunner.signup({
            user_data: {
                id: "123", // Required: User ID
                name: "John Doe", // Optional
                phone: "9876543210", // Optional
                email: "user@example.com", // Optional
                // These properties are used to track reinstalls
                user_created_at: "2024-01-01T00:00:00Z", // Optional
                is_first_time_user: true, // Optional
                mixpanel_distinct_id: "mixpanel_distinct_id", // Optional - Mixpanel Distinct ID
                amplitude_device_id: "amplitude_device_id", // Optional - Amplitude User ID
                posthog_distinct_id: "posthog_distinct_id", // Optional - PostHog Distinct ID
            },
            data: {}, // Optional: Any additional data
        });
        console.log("Signup successful");
    } catch (error) {
        console.error("Error during signup:", error);
    }
};

To enable remarketing and reattribution, you need to capture deep links and pass them to the Linkrunner SDK. This allows Linkrunner to detect returning users who open the app via a deep link.

javascript
import { useEffect } from 'react';
import { Linking } from 'react-native';
import linkrunner from 'rn-linkrunner';

function App() {
  useEffect(() => {
    // Cold start — app was launched by a deeplink
    Linking.getInitialURL().then((url) => {
      if (url) {
        linkrunner.handleDeeplink(url);
      }
    });

    // Warm start — app was in background, deeplink brought it to foreground
    const subscription = Linking.addEventListener('url', ({ url }) => {
      linkrunner.handleDeeplink(url);
    });

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

  return (
    // your app content
  );
}

Linkrunner sends the updated deeplink back after processing. For Linkrunner campaign links, use the returned deeplink as the resolved destination instead of the original tracking URL.

json
{
    "deeplink": "https://app.yourdomain.com/product/123"
}

Getting Attribution Data ​

Using Amplitude for install or pre-login onboarding reports? Send attribution from your app before tracking those events. The automatic signup integration does not add campaign properties to earlier events.

To get attribution data and deeplink information for the current installation, use the getAttributionData function:

javascript
const getAttributionInfo = async () => {
    try {
        const attributionData = await linkrunner.getAttributionData();
        console.log("Attribution data:", attributionData);
    } catch (error) {
        console.error("Error getting attribution data:", error);
    }
};

The getAttributionData function returns:

typescript
{
    deeplink?: string;
    campaignData?: {
        id: string;
        name: string;
        type: string; // "ORGANIC" | "INORGANIC"
        adNetwork?: string | null; // "META" | "GOOGLE" | "APPLE_SEARCH_ADS" | "TIKTOK" | "SNAPCHAT" | null
        installedAt: string;
        storeClickAt?: string | null;
        groupName?: string;
        assetName?: string;
        assetGroupName?: string;
        adNetworkCampaignId?: string | null; // Ad network campaign ID
        adSetId?: string | null; // Ad set ID
        adSetName?: string | null; // Ad set name
        adCreativeId?: string | null; // Ad creative ID
        adCreativeName?: string | null; // Ad creative name
    };
    gaid?: string | null; // Android only
    idfa?: string | null; // iOS only, null when ATT is not authorized
}

gaid is set on Android and idfa on iOS. Each one is null when it is not available, for example on the other platform, when the user has not allowed tracking (ATT), or when limited ad tracking is on. Requires rn-linkrunner 3.2.1 or later.

Example response:

json
{
    "deeplink": "https://app.yourdomain.com/product/123",
    "campaignData": {
        "id": "camp_123",
        "name": "Summer Sale 2024",
        "type": "INORGANIC",
        "adNetwork": "META",
        "installedAt": "2024-03-20T10:30:00Z",
        "storeClickAt": "2024-03-20T10:29:45Z",
        "groupName": "Summer Sale Group",
        "assetName": "Banner Ad 1",
        "assetGroupName": "Product Catalog",
        "adNetworkCampaignId": "120214682829390250",
        "adSetId": "120214682829640250",
        "adSetName": "Productivity",
        "adCreativeId": "120214682926100250",
        "adCreativeName": "Static_2"
    },
    "gaid": "38400000-8cf0-11bd-b23e-10b96e40000d",
    "idfa": null
}

Setting User Data ​

Call setUserData to send user details and the IDs your product analytics tools use for this user. You can call it right after init, before the user logs in: id is optional for setUserData. Pass id once the user is logged in.

When you pass a tool's ID and that integration is configured in Linkrunner, Linkrunner sends this install's attribution to that tool for this user, the same attribution it sends after signup (for example, lr_campaign and lr_ad_network on the Mixpanel profile). This applies to installs attributed to a campaign. If the install is not attributed yet, Linkrunner sends the properties once attribution completes. Supported IDs:

ToolField
Mixpanelmixpanel_distinct_id
Amplitudeamplitude_device_id
PostHogposthog_distinct_id
MoEngagemoengage_user_id
Brazebraze_device_id
Google Analytics 4ga_app_instance_id and ga_session_id
Netcorenetcore_device_guid

setUserData is not a replacement for signup. Call signup as soon as the user is identified (signup or login). signup still requires id.

Calling setUserData without id requires React Native SDK 3.3.0 or later. On earlier versions id is required.

Right after init, before the user logs in:

javascript
const setAnalyticsIds = async (mixpanelDistinctId) => {
    await linkrunner.setUserData({
        mixpanel_distinct_id: mixpanelDistinctId,
    });
};

After the user logs in:

javascript
const setUserData = async () => {
    await linkrunner.setUserData({
        id: "123", // Your customer user ID
        name: "John Doe", // Optional
        phone: "9876543210", // Optional
        email: "user@example.com", // Optional
        mixpanel_distinct_id: "mixpanel_distinct_id", // Optional
    });
};

Setting CleverTap ID ​

Use the setAdditionalData method to set CleverTap ID:

javascript
const setIntegrationData = async () => {
    await linkrunner.setAdditionalData({
        clevertapId: "YOUR_CLEVERTAP_USER_ID", // CleverTap user identifier
    });
};

Parameters for linkrunner.setAdditionalData ​

  • clevertapId: string (optional) - CleverTap user identifier

This method allows you to connect user identities across different analytics and marketing platforms.

Revenue Tracking ​

Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Pass your user ID as userId so each payment is linked to the user who paid. You can verify your events are being captured on the Events page.

Tracking Payments ​

A payment is an in-app event with revenue. Send it with trackEvent, and put amount, paymentId, and currency in eventData. The event name becomes the payment type in reports and mappings, so you can use any name, such as purchase or SUBSCRIPTION_RENEWED. Requires rn-linkrunner 4.0.0 or later. For examples for every case and how payments reach ad networks, see the full Track payments guide.

This example sends every payment key. amount, paymentId, currency, and userId are required for a payment.

javascript
import linkrunner, { PaymentStatus } from "rn-linkrunner";

const trackPurchase = async () => {
  try {
    await linkrunner.trackEvent(
      "purchase", // Payment name, stored as the payment type in reports and mappings
      {
        amount: 1499,                            // Required for a payment: total the user paid, a finite number >= 0
        paymentId: "order_123",                  // Required for a payment: your order or transaction ID, used to deduplicate
        currency: "INR",                         // Required for a payment: 3-letter ISO 4217 code
        userId: "user_123",                      // Required for a payment: your user ID. "" uses the user saved by signup or setUserData
        status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below)
        // Optional: any other keys stay as event data. Meta and Google read these product fields
        content_ids: ["sku_1", "sku_2"],
        contents: [
          { id: "sku_1", quantity: 1, item_price: 999 },
          { id: "sku_2", quantity: 1, item_price: 500 },
        ],
        content_type: "product",
        num_items: 2,
        order_id: "order_123",
      },
      "evt_order_123", // Optional: your own ID for this event
    );
  } catch (error) {
    // On Android an invalid payment throws here, and nothing is sent
    console.error("Error tracking payment:", error);
  }
};

Payment keys in eventData ​

KeyTypeRequiredDescription
amountnumberYesTotal the user paid. Must be a finite number, 0 or greater. This is the revenue value.
paymentIdstringYesYour order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again.
currencystringYes3-letter ISO 4217 currency code, such as INR or USD. Case-insensitive, sent in uppercase.
userIdstringYesYour ID for the user who paid. If you pass an empty string, the SDK uses the user saved by signup or setUserData.
statusstringNoPAYMENT_INITIATED, PAYMENT_COMPLETED (default), PAYMENT_FAILED, or PAYMENT_CANCELLED.
Any other keyAnyNoStays in the event data, such as product details for ecommerce events.

How trackEvent decides:

  • amount, paymentId, and currency are all in eventData: the event is a payment. The SDK checks the payment keys and sends them as payment fields.
  • None of them, or amount or currency without paymentId: the event is a normal custom event, exactly as before. An amount or currency key on its own is ordinary event data.
  • paymentId with amount or currency missing: trackEvent throws with amount, paymentId and currency must be sent together, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason.
  • A negative or non-numeric amount, a blank paymentId, a currency that is not 3 letters, or an unknown status: trackEvent throws, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason.

Payment status ​

rn-linkrunner exports PaymentStatus as an object of string values. Plain strings such as "PAYMENT_COMPLETED" also work.

StatusUse it whenEffect
PAYMENT_INITIATEDThe user started paying, such as opening the checkout or payment sheet.Stored for reports and analytics. Not added to revenue and not sent to ad networks.
PAYMENT_COMPLETED (default)The charge succeeded.Added to revenue and paying users. Sent to ad networks when the event name is mapped.
PAYMENT_FAILEDThe charge was declined or errored.Stored for reports and analytics. Not added to revenue and not sent to ad networks.
PAYMENT_CANCELLEDThe user backed out before paying, or you refunded it. Send it with the same event name, paymentId, amount, and currency as the completed payment.Stored for reports and analytics. The payment no longer counts as revenue and nothing is sent to ad networks.

Linkrunner keeps one payment per paymentId, event name, and status, so a retry with the same values is recorded once. See Payment status for a full lifecycle example.

The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as FIRST_PAYMENT or DEFAULT, so your existing mappings and reports keep working. A different spelling such as first_payment is a new payment type. Any other name, such as purchase, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks.

capturePayment and removePayment are deprecated. They still work in 4.0.0 and show a deprecation warning, and they will be removed in a future major version. Use trackEvent with the payment keys instead. Apps on rn-linkrunner 3.x keep working unchanged. To move, see Migrate payments to in-app events.

Ecommerce Events ​

Minimum SDK Version: Ecommerce Event Manager requires rn-linkrunner v2.7.0 or above. Please ensure your SDK is updated before using this feature.

If you are tracking Ecommerce events to sync with Meta or Google, you must format your eventData to include the required fields. You also need to map your custom event to the standard commerce event in the Linkrunner Dashboard.

For detailed explanations of the required fields like content_ids, contents, and value, refer to our Meta Commerce Manager documentation or Google Commerce Manager documentation.

Add To Cart Example ​

Use the trackEvent method to send an AddToCart event:

javascript
const trackAddToCart = async () => {
  try {
    await linkrunner.trackEvent(
      "add_to_cart", // Map this custom event to "AddToCart" (Meta) or "add_to_cart" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager
      {
        content_ids: ["product_123"],
        contents: [
          {
            id: "product_123", // Matches content_ids
            quantity: 1,
            item_price: 49.99,
          },
        ],
        content_type: "product",
        currency: "USD",
        value: 49.99,
        num_items: 1,
      },
    );
    console.log("Add To Cart event tracked successfully");
  } catch (error) {
    console.error("Error tracking Add To Cart event:", error);
  }
};

View Content Example ​

Use the trackEvent method to send a ViewContent event:

javascript
const trackViewContent = async () => {
  try {
    await linkrunner.trackEvent(
      "view_item", // Map this custom event to "ViewContent" (Meta) or "view_item" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager
      {
        content_ids: ["product_123"],
        contents: [
          {
            id: "product_123", // Matches content_ids
            quantity: 1,
            item_price: 49.99,
          },
        ],
        content_type: "product",
        currency: "USD",
        value: 49.99,
        num_items: 1,
      },
    );
    console.log("View Content event tracked successfully");
  } catch (error) {
    console.error("Error tracking View Content event:", error);
  }
};

Payment / Purchase Example ​

Use trackEvent with the payment keys and the ecommerce fields in eventData to send a Purchase event:

javascript
const trackPurchase = async () => {
  try {
    await linkrunner.trackEvent("purchase", { // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard
      amount: 49.99, // Order total, sent to ad networks as the value
      paymentId: "payment_456",
      currency: "USD",
      userId: "user_123",
      status: "PAYMENT_COMPLETED", // Optional, the default
      content_ids: ["product_123"],
      contents: [{ id: "product_123", quantity: 1, item_price: 49.99 }],
      content_type: "product",
      num_items: 1,
      order_id: "order_abc123", // Required for Purchase events
    });
  } catch (error) {
    console.error("Error tracking payment:", error);
  }
};

Note: For more information on testing and verifying your ecommerce events, please see our Meta Commerce Manager or Google Commerce Manager guide.

Tracking Custom Events ​

From React Native SDK v2.11.0, custom events automatically include the user_id you set during signup() / setUserData(). The SDK stores this identifier securely on-device and attaches it to every trackEvent call, so you no longer need to pass it manually. Events tracked before signup are sent without a user_id.

Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call .signup so events are linked to a user. You can verify your events are being captured on the Events page. To record revenue, add the payment keys to eventData.

Track custom events in your app:

javascript
const trackEvent = async () => {
    await linkrunner.trackEvent(
        "purchase_initiated", // Event name
        { product_id: "12345", category: "electronics" }, // Optional: Event data
        "order_12345" // Optional: Your own unique event identifier (string or number)
    );
};

Parameters for linkrunner.trackEvent ​

  • eventName: string (required) - Name of the event to track
  • eventData: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties. Add amount, paymentId, currency, and userId to make it a payment. See Tracking Payments.
  • eventId: string | number (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend

Revenue Sharing with Ad Networks ​

Send revenue to ad networks such as Google Ads and Meta as a payment. Meta and Google receive the value and currency from the payment amount and currency, so send the order total as amount. Linkrunner does not add up item prices.

Put the payment keys and the product details in eventData. Meta receives content_ids, contents, content_type, num_items, and order_id. Google receives content_ids as item IDs.

javascript
const trackPurchase = async () => {
  try {
    await linkrunner.trackEvent("purchase", {
      amount: 149.99, // Order total, sent to ad networks as the value
      paymentId: "order_12345",
      currency: "USD",
      userId: "user_123",
      content_ids: ["product_123"],
      num_items: 1,
      order_id: "order_12345",
    });
  } catch (error) {
    console.error("Error tracking payment:", error);
  }
};

An amount in eventData is revenue only when paymentId and currency are there too. On its own it is ordinary event data. Revenue is shared only when Send Revenue is turned on for the event's mapping in the dashboard. See Meta Ads and Google Ads.

SDK Package Version ​

Use linkrunner.getPackageVersion() in diagnostics when you need the installed rn-linkrunner package version.

On iOS, use linkrunner.enableTCFConsentCollection(true) only if you use an IAB TCF-compliant consent management platform and choose to let the SDK read its consent signals. Call it on every launch before init; the setting is not persisted. Explicit values passed to setConsent take precedence. This method has no effect on Android.

Enhanced Privacy Controls ​

The SDK offers options to enhance user privacy:

javascript
// Enable PII (Personally Identifiable Information) hashing
linkrunner.enablePIIHashing(true);

When PII hashing is enabled, sensitive user data like name, email, and phone number are hashed using SHA-256 before being sent to Linkrunner servers.

Privacy mode ​

Use privacy mode for apps directed at children. It stops the native SDKs from collecting any device-level identifier. On Android: no Google Advertising ID, App Set ID or Android ID, no local IP address, no carrier name and no Meta install referrer. On iOS: no IDFA, no ATT prompt, no IDFV, no Google on-device conversion value, no user-assigned device name and no SKAdNetwork conversion values. Only the SDK's install instance ID identifies the install.

It needs native Android SDK 5.1.0 and iOS SDK 5.1.0. Use rn-linkrunner 4.1.0 or later.

Pass true as the sixth argument of init, after debug:

javascript
await linkrunner.init(
    "YOUR_PROJECT_TOKEN",
    undefined, // secretKey
    undefined, // keyId
    false, // disableIdfa
    false, // debug
    true // privacyMode
);

The SDK saves the setting. true stays on across launches until you call init with privacyMode set to false. If you leave the argument out, the SDK keeps the saved value.

When privacy mode is on:

In Expo, also set privacyMode in the config plugin. See Privacy mode in Expo.

For everything privacy mode removes, what is still sent, and how it changes your store privacy answers, see Privacy mode.

Uninstall Tracking ​

Before you begin ​

Here's what you need to know before getting started:

Requirements:

Android ​

Connect Firebase Cloud Messaging (FCM) with Linkrunner

FCM HTTP v1 API

To configure FCM HTTP V1 for uninstalls:

Enable the FCM API:

  1. Go to the FCM console.
  2. Select a project.
  3. Go to Project Overview > Project settings.
  4. Copy the Project ID. This will be required in a later step. Project ID
  5. Go to the Cloud Messaging tab.
  6. Make sure that Firebase Cloud Messaging API (V1) is set to Enabled.

Create a custom role for Linkrunner Uninstall:

  1. Go to the Service accounts tab.
  2. Click Manage service account permissions.
  3. A new browser tab opens in Google Cloud Platform.
  4. In the side menu, select Roles.
  5. Click + Create role.
  6. Enter the following details:
    • Title: Linkrunner Uninstalls
    • ID: lr_uninstalls
    • Role launch stage: General availability
  7. Click + Add permissions.
  8. In Enter property name or value field, enter cloudmessaging.messages.create and select it from the search results. Google Cloud Permission
  9. Check the cloudmessaging.messages.create option and click Add.
  10. Click Create.

Assign Linkrunner the FCM uninstall role:

  1. In the side menu, select IAM.
  2. Open the View by Principals tab.
  3. Click Grant Access.
  4. In Add Principals -> New principals field, enter lr-uninstalls-tracking@lr-uninstalls-tracking.iam.gserviceaccount.com
  5. In Assign Roles -> Select a role field, enter Linkrunner Uninstalls and select it from the search results.
  6. Click Save.

The Linkrunner service account has been assigned the role of Linkrunner Uninstalls.

Linkrunner Dashboard
  1. In Linkrunner, go to Settings > Uninstall Tracking.
  2. Under the Android tab, enter the Firebase Project ID that you copied initially and click Save.

Uninstall Tracking

Integrate with Linkrunner SDK

Follow these instructions to integrate FCM with the Linkrunner SDK:

  1. Set up Firebase Cloud Messaging:

Set up Firebase Cloud Messaging in your React Native app. See the React Native Firebase documentation for detailed instructions.

  1. Configure your app to provide the device's push token to the Linkrunner SDK.
javascript
import messaging from '@react-native-firebase/messaging';
import linkrunner from 'rn-linkrunner';

// Fetch FCM token and set in Linkrunner SDK
const initializePushToken = async () => {
    const token = await messaging().getToken();
    if (token) {
        await linkrunner.setPushToken(token);
    }
};

// Listen for token refresh
const setupTokenRefresh = () => {
    messaging().onTokenRefresh(async (token) => {
        await linkrunner.setPushToken(token);
    });
};

// Handle incoming messages
const setupMessageListener = () => {
    messaging().onMessage(async (remoteMessage) => {
        if (remoteMessage.data && remoteMessage.data['lr-uninstall-tracking']) {
            // Silent notification for uninstall tracking, ignore
            return;
        }
        // Handle other messages here
    });
};

Custom implementations of FCM's message handler can unintentionally make uninstall push notifications visible to users, disrupting the intended silent experience. To avoid this, ensure your logic checks if the message contains lr-uninstall-tracking and handles it accordingly, as shown in the code example above.

iOS ​

Connect APNs with Linkrunner

Apple Developer Portal

Get the required credentials from the Apple Developer Portal:

APNs Authentication Key (p8) and Key ID:

  • Go to the Apple Developer Portal.
  • Select Identifiers under Certificates, IDs & Profiles.
  • Click on the app you want to track uninstalls for. Then, under Capabilities, search for Push Notifications and enable it.
  • Under Certificates, IDs & Profiles, select Keys and click on plus (+) icon to create a key. Enable APNs when creating the key and download the key file (p8).
  • The Key ID can be found in the Keys tab.

Bundle ID and Team ID:

  • Under Identifiers, click on your app and you will see the Bundle ID and Team ID (App ID Prefix).
Linkrunner Dashboard
  1. In Linkrunner, go to Settings > Uninstall Tracking.
  2. Under the iOS tab, upload the APNs Authentication Key (p8) file and enter the Key ID, Bundle ID and Team ID (App ID Prefix) that you copied from the Apple Developer Portal.

Uninstall Tracking

Integrate with Linkrunner SDK

Follow these instructions to integrate APNs with the Linkrunner SDK:

  1. Set up Firebase Cloud Messaging:

Set up Firebase Cloud Messaging in your React Native app if you haven't already. See the React Native Firebase documentation for detailed instructions.

  1. Configure your app to provide the device's APNs token to the Linkrunner SDK.
javascript
import messaging from '@react-native-firebase/messaging';
import linkrunner from 'rn-linkrunner';
import { Platform } from 'react-native';

const initializeAPNsToken = async () => {
    if (Platform.OS === 'ios') {
        const apnsToken = await messaging().getAPNSToken();
        if (apnsToken) {
            await linkrunner.setPushToken(apnsToken);
        }
    }
};

Function Placement Guide ​

FunctionRequirementWhere to PlaceWhen to Call
linkrunner.initRequiredApp.tsx within useEffectOnce when app starts
linkrunner.signupRequiredIdentification flow (signup or login)Once when the user is identified
linkrunner.setCustomerUserIdRequiredAfter init, when your user ID is availableAs early as the user's ID is available
linkrunner.handleDeeplinkRequiredDeep link entry pointsWhen app is opened via a deep link
linkrunner.getAttributionDataOptionalAttribution data handling flowWhenever the attribution data is needed
linkrunner.setAdditionalDataOptionalIntegration codeWhen third-party integration IDs are available
linkrunner.setUserDataOptionalApp start, authentication or profile update logicRight after init with your analytics IDs, and when user details become available
linkrunner.trackEventOptionalThroughout app, including ecommerce flowsWhen specific user actions occur, including AddToCart or ViewContent events
linkrunner.trackEvent with payment keysOptionalPayment processing, including ecommerce purchasesWhen user makes a payment, including Purchase events with ecommerce data
linkrunner.setPushTokenOptionalPush notification setupWhen FCM/APNs token is available
linkrunner.setConsentOptionalApp initialization or consent flowBefore init, and again when consent changes
linkrunner.enablePIIHashingOptionalPrivacy configurationWhen enabling PII hashing
linkrunner.enableTCFConsentCollectionOptionaliOS consent configurationOn every launch before init, if you opt into TCF consent collection
linkrunner.getPackageVersionOptionalDiagnosticsWhen you need the installed SDK package version

Publishing to Google Play or the App Store? See Data the SDK collects for what the SDK sends and the answers for the Play Data safety form and Apple's App Privacy questions.

Next Steps ​

Support ​

If you encounter issues during integration, contact us at support@linkrunner.io.