diff --git a/ad-networks/google-ads-discrepancies.mdx b/ad-networks/google-ads-discrepancies.mdx index 6dfd40f..3cbfbee 100644 --- a/ad-networks/google-ads-discrepancies.mdx +++ b/ad-networks/google-ads-discrepancies.mdx @@ -21,7 +21,7 @@ Walk through this checklist. -Open [Settings → Google Ads integration](https://dashboard.linkrunner.io/settings?s=google-ads-integration) and verify the Link ID is saved for the correct platform (Android or iOS). +Open [Integrations → Google Ads](https://dashboard.linkrunner.io/dashboard/integrations/google-ads?p_id=89), open the three-dot menu on the Google Ads row, and verify the link ID is saved for the correct platform (Android or iOS). On the Google Ads [Conversions summary](https://ads.google.com/aw/conversions) page, confirm the `first_open` conversion event is imported. If it was previously deleted, follow [Re-enabling Previously Deleted Events](/ad-networks/google-ads#re-enabling-previously-deleted-events). Full setup: [Importing Events for Conversion Actions Setup](/ad-networks/google-ads#4-importing-events-for-conversion-actions-setup). @@ -80,6 +80,7 @@ Google is a Self-Reporting Network (SRN), so Google Ads reports its own attribut - **Android:** Requires GAID collection. The Linkrunner SDK captures GAID automatically when the user has not opted out. - **iOS:** Measurement happens via **SKAdNetwork (SKAN)**. Google Ads sends SKAN postbacks to Linkrunner, and installs with `did_win=TRUE` are counted. Deterministic SDK attribution only works when the user grants ATT and the IDFA is available. See [SKAdNetwork integration](/features/skadnetwork-integration) and [iOS Campaign Data Limitations](/features/ios-campaign-data-limitations). +- **iOS without ATT consent:** [Integrated Conversion Measurement](/sdk/ios#google-integrated-conversion-measurement-optional) can attribute installs even when ATT is denied and no IDFA is available. It complements SKAN rather than replacing it, and needs setup in your iOS app. Google keeps it inactive for users in the EEA, the UK, and Switzerland. See the [Google Ads integration guide](/ad-networks/google-ads) for full setup on each platform. @@ -98,7 +99,7 @@ Google Ads dates conversions to the **ad click**. Linkrunner dates them to the * As an SRN, Google attributes every install that follows any Google Ads engagement within its window. Linkrunner uses **last-click attribution**, deduplicates across networks, and counts one install per user. -On iOS, Linkrunner counts only SKAN postbacks with `did_win=TRUE`. Google Ads treats all SKAN events the same, so its dashboard typically reports more iOS installs. +On iOS, Linkrunner counts only SKAN postbacks with `did_win=TRUE`. Google Ads treats all SKAN events the same, so its dashboard typically reports more iOS installs. [Integrated Conversion Measurement](/sdk/ios#google-integrated-conversion-measurement-optional) narrows this gap for users who declined ATT. diff --git a/ad-networks/google-ads.mdx b/ad-networks/google-ads.mdx index 85da2cc..ae9febc 100644 --- a/ad-networks/google-ads.mdx +++ b/ad-networks/google-ads.mdx @@ -63,10 +63,10 @@ To connect your mobile app with Google Ads, you'll need to create a link ID that ### Step 3: Configure in Linkrunner Dashboard 1. Copy the received link ID from Google Ads -2. Navigate to your Linkrunner Dashboard -3. Go to [Settings → Google Ads integration](https://dashboard.linkrunner.io/settings?s=google-ads-integration) -4. Paste the link ID in the designated field -5. Save your configuration +2. Go to [Integrations → Google Ads](https://dashboard.linkrunner.io/dashboard/integrations/google-ads?p_id=89) in your Linkrunner dashboard +3. Open the three-dot menu on the Google Ads row and choose the link IDs option +4. Paste the link ID into **Android Link ID** or **iOS Link ID**, matching the platform you selected in Google Ads +5. Click **Save Link IDs** ![Google Ads Dashboard - Link ID Created](/images/google_ads/google-ads-dashboard-3.webp) @@ -134,6 +134,12 @@ After creating the link ID and mapping events in Linkrunner, you can import app events. + + Running App Campaigns for an iOS app? Set up [Google Integrated Conversion + Measurement](/features/google-icm) so Google can attribute installs from users who declined App Tracking + Transparency. + + ## FAQ and Discrepancies For answers to common questions and an explanation of why numbers in Google Ads may not exactly match Linkrunner, see [Google Ads FAQ and Discrepancies](/ad-networks/google-ads-discrepancies). diff --git a/docs.json b/docs.json index fa2f5da..09e17ce 100644 --- a/docs.json +++ b/docs.json @@ -70,6 +70,8 @@ "features/uninstall-tracking", "features/retention", "features/meta-install-referrer", + "features/google-icm", + "features/send-consent", "features/ios-campaign-data-limitations", "features/webhooks", "features/mcp", diff --git a/features/google-icm.mdx b/features/google-icm.mdx new file mode 100644 index 0000000..1979c45 --- /dev/null +++ b/features/google-icm.mdx @@ -0,0 +1,120 @@ +--- +title: "Google Integrated Conversion Measurement" +description: "Recover Google App Campaign attribution on iOS when no click identifier or advertising ID is available" +icon: "google" +sidebarTitle: "Google ICM" +--- + +## What it is + +Google App Campaigns lose iOS installs when there is no click identifier and no IDFA to match on, which is the normal state once a user declines App Tracking Transparency. Integrated Conversion Measurement (ICM) closes that gap. + +Google's [On-Device Measurement (ODM) SDK](https://github.com/googleads/google-ads-on-device-conversion-ios-sdk) keeps the ad click context on the device and turns it into an encrypted value, `odm_info`. Linkrunner reads that value when the SDK initializes and sends it with the install, so Google can match the conversion without an identifier ever leaving the device. + +Set this up if you run Google App Campaigns for your iOS app. Google's SDK requires iOS 12 or later, and Google reports the improved coverage for users on iOS 14 and later. + + + Google keeps ODM inactive for users located in the European Economic Area, the United Kingdom, and Switzerland. For those users the SDK returns nothing and the field is left out, so ICM recovers no installs there. + + +On Android there is nothing to install. Google applies the same ICM improvements automatically through the App Conversion API, mainly for EEA users and users who opt out of device-level permissions. + + + ICM complements SKAdNetwork, it does not replace it. Keep your [SKAdNetwork integration](/features/skadnetwork-integration) in place. + + +## Prerequisites + +Four things have to be in place. ICM stays inactive until all four are done, and it fails quietly rather than loudly, so work through them in order. + + + + ICM shipped in the 4.1.0 line of the native SDKs. Anything older collects no `odm_info` and has no consent API. + + | Platform | Minimum version | + | --- | --- | + | Android | `io.linkrunner:android-sdk:4.1.0` | + | iOS | LinkrunnerKit 4.1.0 | + | React Native | `rn-linkrunner` 3.1.0 | + | Flutter | `linkrunner` 4.1.0 | + | Expo | `rn-linkrunner` 3.1.0 | + | Unity | LinkrunnerKit 4.1.0 and `io.linkrunner:android-sdk:4.1.0` | + + + + Link IDs are per platform. An Android link ID does not cover your iOS app, and without an iOS one Google has nowhere to send the conversion, so ICM produces nothing no matter what the app does. + + In Google Ads, go to **Tools** → **Data Manager** → **Third-party app analytics**, create a link ID with provider ID `9936233049`, and select the **iOS** platform and your app. + + Then in Linkrunner, go to [Integrations → Google Ads](https://dashboard.linkrunner.io/dashboard/integrations/google-ads?p_id=89), open the three-dot menu on the Google Ads row, and paste it into **iOS Link ID**. Click **Save Link IDs**. + + Full walkthrough with screenshots: [Create Link ID for Android/iOS App](/ad-networks/google-ads#2-create-link-id-for-androidios-app). + + + + Linkrunner does not bundle it. The SDK is detected at runtime, so apps that skip ICM carry no extra dependency and no size increase. + + Your SDK's installation section has the exact pod or package to add, along with any linker flags: [iOS](/sdk/ios#google-integrated-conversion-measurement-optional), [React Native](/sdk/react-native#google-integrated-conversion-measurement-optional), [Flutter](/sdk/flutter#google-integrated-conversion-measurement-optional), [Expo](/sdk/expo#google-integrated-conversion-measurement-optional), [Unity](/sdk/unity#google-integrated-conversion-measurement-optional). + + If your app already uses the Firebase iOS SDK 11.14.0 or later, the `FirebaseAnalytics` pod brings it in and there is nothing to add. + + + + Google's App Conversion API treats the consent parameters as required whenever their value is known, so an install that arrives with no consent state is matched with less information. Call `setConsent` with your consent values before you initialize the SDK, and again whenever the user changes their choice. + + This applies on Android too, even though there is no ODM SDK to add there. See [Consent signals](#consent-signals) below for what each parameter means, and [Send Consent](/features/send-consent) for what to report and how to collect it. + + + +Everything else is automatic. There is no ICM API to call and attribution still comes back through `getAttributionData`. + +## Platform support + +| Platform | Collects `odm_info` | Consent API | +| --- | --- | --- | +| [Android](/sdk/android#google-ads-consent) | Not applicable | `setConsent` | +| [iOS](/sdk/ios#google-integrated-conversion-measurement-optional) | Yes | `setConsent` | +| [React Native](/sdk/react-native#google-integrated-conversion-measurement-optional) | Yes (iOS) | `setConsent` | +| [Flutter](/sdk/flutter#google-integrated-conversion-measurement-optional) | Yes (iOS) | `setConsent` | +| [Expo](/sdk/expo#google-integrated-conversion-measurement-optional) | Yes (iOS) | `setConsent` | +| [Unity](/sdk/unity#google-integrated-conversion-measurement-optional) | Yes (iOS) | `setConsent` on both platforms | +| Capacitor, Cordova | Not yet supported | No | + + + ODM is an iOS-only SDK. There is no Android equivalent and none is needed, because Android installs reach Google through the App Conversion API. That API reads the same consent signals, so `setConsent` still matters on Android. + + +## Consent signals + +`setConsent` takes three signals, each of which is `granted`, `denied`, or `unknown`. Linkrunner forwards them to Google with your installs and events. + +| Parameter | Sent to Google as | What it means | +| --- | --- | --- | +| `isEEA` | `eea` | European regulations apply to this user (the EEA, the UK, or Switzerland) | +| `hasConsentForDataUsage` | `ad_user_data` | The user agreed to their data being sent to Google for advertising | +| `hasConsentForAdsPersonalization` | `ad_personalization` | The user agreed to their data being used to personalize ads | + +A signal left `unknown` is dropped from the request rather than sent as a denial, so Linkrunner never reports a choice your user did not make. Never map `unknown` to `granted`. + +App Tracking Transparency is not a substitute: it governs IDFA access, not Google's use of ad user data or personalization, and Linkrunner reports ATT status separately. + +See [Send Consent](/features/send-consent) for what to report for users outside the EEA, and how to collect consent manually or from a Consent Management Platform. + +## Set it up + + + + Report consent for the App Conversion API + + + Add the ODM SDK and report consent + + + Add the pod and report consent + + + Add the pod and report consent + + + +**Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io) diff --git a/features/send-consent.mdx b/features/send-consent.mdx new file mode 100644 index 0000000..8381ab0 --- /dev/null +++ b/features/send-consent.mdx @@ -0,0 +1,179 @@ +--- +title: "Send Consent" +description: "Report DMA consent signals with your installs and events" +icon: "user-shield" +sidebarTitle: "Send Consent" +--- + +## What this consent is for + +Google's [EU User Consent Policy](https://www.google.com/about/company/user-consent-policy/) and the EU **Digital Markets Act (DMA)** require you to tell Google what a user agreed to before Google may use their conversion. Enforcement began on 6 March 2024. + +It applies to end users located in the **European Economic Area** (the 27 EU states plus Iceland, Liechtenstein, and Norway), the **United Kingdom**, and **Switzerland**. It follows the user's location, not where your company is based. + +Linkrunner forwards these values with every install and event it sends to Google. Report nothing for an in-scope user and Google has less to match on, so conversions can be dropped and campaign performance suffers. + +**By default Linkrunner sends nothing.** Every signal starts as `unknown` until you set it, and unknown signals are left out of the request entirely. Linkrunner never fills in a default on your behalf, in either direction, so a user's consent state is only ever what your app reported. + + + Other privacy laws (India's DPDP Act, Brazil's LGPD, and so on) are separate frameworks with no Google conversion parameter. For a user outside the EEA, the UK, and Switzerland, report `isEEA` as denied and leave the other two unset. + + +## The three signals + +| SDK field | Sent to Google as | Denied (`0`) | Granted (`1`) | +| --- | --- | --- | --- | +| `isEEA` | `eea` | European regulations do not apply to this user | They do apply | +| `hasConsentForDataUsage` | `ad_user_data` | The user refused sending user-level data to Google | The user agreed | +| `hasConsentForAdsPersonalization` | `ad_personalization` | The user refused ads personalization | The user agreed | + +Each signal is `granted`, `denied`, or `unknown`. Google treats all three as required whenever their value is known, which cuts both ways: + +- A value you know **must** be sent. +- A value you do not know **must not** be invented. + +Linkrunner drops any signal left `unknown` instead of sending it as a denial, so a choice your user never made is never reported on their behalf. Never map `unknown` to `granted`. + + + Google also requires `lat` (limit ad tracking), but you do not set it. Linkrunner derives it from the device. A missing advertising ID is not a consent signal and is never treated as one. + + +## What to report + + + **For users outside the EEA, the UK, and Switzerland, report `isEEA` as denied and leave the other two unset.** The DMA does not apply to them, so `ad_user_data` and `ad_personalization` are not required. + + + + + Report `isEEA` as denied and leave the other two unset. The DMA does not apply, so `ad_user_data` and `ad_personalization` are not required for this user. Send them only if you genuinely know them. + + ```kotlin + LinkRunner.getInstance().setConsent( + LinkrunnerConsent(isEEA = ConsentStatus.DENIED) + ) + ``` + + The two omitted fields default to `UNKNOWN` and are dropped from the payload. + + + + Report all three from the answers your consent screen actually collected. + + ```kotlin + LinkRunner.getInstance().setConsent( + LinkrunnerConsent( + isEEA = ConsentStatus.GRANTED, + hasConsentForDataUsage = ConsentStatus.GRANTED, + hasConsentForAdsPersonalization = ConsentStatus.DENIED + ) + ) + ``` + + The values above are only an example of the shape of the call, not what you should send. Report whatever the user actually chose. Denying personalization while granting data usage is a normal combination, not a mistake. + + + + Leave the signal `unknown`, which is what it is before you set anything. Linkrunner omits it. + + ```kotlin + LinkRunner.getInstance().setConsent( + LinkrunnerConsent(isEEA = ConsentStatus.UNKNOWN) + ) + ``` + + This is the correct state before your consent screen has been answered. Call `setConsent` again once the user chooses. + + + + + Do not guess the region. If you cannot determine whether the user is in scope, leave `isEEA` unknown rather than assuming denied. Reporting a European user as non-European applies the wrong rule set to their conversion. + + +## Collecting consent + +A **Consent Management Platform (CMP)** shows the consent screen at first launch and stores the answers, or you can build the screen yourself. Either way, you read the answers and report them with `setConsent`. + + + + Show your consent screen at first launch, before you initialize the SDK. Ask whether they agree to their data being sent to Google for advertising, and whether they agree to it being used to personalize ads. + + + + Most CMPs detect this for you. Otherwise resolve it server side from the IP address. If you cannot resolve it, leave `isEEA` unknown. + + + + Map each answer to `granted`, `denied`, or `unknown`, then pass them in. The first payload has to carry the right state, so this must run before `init`. + + The values below are an example. Send what the user actually chose, never a hardcoded set. + + + + ```kotlin + import io.linkrunner.sdk.models.ConsentStatus + import io.linkrunner.sdk.models.LinkrunnerConsent + + LinkRunner.getInstance().setConsent( + LinkrunnerConsent( + isEEA = ConsentStatus.GRANTED, + hasConsentForDataUsage = ConsentStatus.GRANTED, + hasConsentForAdsPersonalization = ConsentStatus.DENIED + ) + ) + ``` + + + ```swift + LinkrunnerSDK.shared.setConsent( + LinkrunnerConsent( + isEEA: .granted, + hasConsentForDataUsage: .granted, + hasConsentForAdsPersonalization: .denied + ) + ) + ``` + + + ```javascript + linkrunner.setConsent({ + isEEA: "granted", + hasConsentForDataUsage: "granted", + hasConsentForAdsPersonalization: "denied", + }); + ``` + + + ```dart + await LinkRunner().setConsent( + LRConsent( + isEEA: ConsentStatus.GRANTED, + hasConsentForDataUsage: ConsentStatus.GRANTED, + hasConsentForAdsPersonalization: ConsentStatus.DENIED, + ), + ); + ``` + + + ```javascript + linkrunner.setConsent({ + isEEA: "granted", + hasConsentForDataUsage: "granted", + hasConsentForAdsPersonalization: "denied", + }); + ``` + + + ```csharp + LinkrunnerSDK.SetConsent("granted", "granted", "denied"); + ``` + + + + + + Consent is stored between launches, so the last value you set keeps being sent. If the user reopens your privacy settings and withdraws consent, call `setConsent` again or you will keep reporting the old answer. + + + +**Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io) diff --git a/sdk/android.mdx b/sdk/android.mdx index 4600e66..031591b 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -28,7 +28,7 @@ Add the Linkrunner SDK to your app's `build.gradle` file: ```gradle dependencies { - implementation 'io.linkrunner:android-sdk:3.10.0' + implementation 'io.linkrunner:android-sdk:4.1.0' } ``` @@ -732,6 +732,47 @@ private fun capturePurchase() { > **Note:** For more information on testing and verifying your ecommerce events, please see our [Testing Ecommerce Events](/ecommerce-manager/meta-commerce-manager#testing-ecommerce-events) guide. +## Google Ads Consent + + + **SDK Version Requirement:** Requires Android SDK version 4.1.0 or higher. + + +If you run Google App Campaigns, report your users' consent choices. Google's App Conversion API treats these values as required whenever they are known, and reads them when it matches your installs, so an install with no consent state attached is harder to attribute. This matters most for users in the European Economic Area, the United Kingdom, and Switzerland. + +Set the values with `setConsent` before you call `init`, and again whenever the user changes their choice: + +```kotlin +import io.linkrunner.sdk.models.ConsentStatus +import io.linkrunner.sdk.models.LinkrunnerConsent + +LinkRunner.getInstance().setConsent( + LinkrunnerConsent( + isEEA = ConsentStatus.GRANTED, + hasConsentForDataUsage = ConsentStatus.GRANTED, + hasConsentForAdsPersonalization = ConsentStatus.DENIED + ) +) +``` + +| Parameter | Meaning | +| --- | --- | +| `isEEA` | European regulations apply to this user (the EEA, the UK, or Switzerland) | +| `hasConsentForDataUsage` | The user agreed to their data being sent to Google for advertising | +| `hasConsentForAdsPersonalization` | The user agreed to their data being used to personalize ads | + +Each takes `ConsentStatus.GRANTED`, `ConsentStatus.DENIED`, or `ConsentStatus.UNKNOWN`. Anything left `UNKNOWN` is dropped from the payload rather than reported as a denial, so Linkrunner never reports a choice your user did not make. + +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](/features/send-consent). A missing advertising ID is not a consent signal and is never treated as one. + + + 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. + + + + There is no extra SDK to add to your Android build. Android installs reach Google through the App Conversion API, which reads the consent signals above. + + ## Enhanced Privacy Controls The SDK offers options to enhance user privacy: @@ -923,6 +964,7 @@ Connect Firebase Cloud Messaging (FCM) with Linkrunner | `LinkRunner.getInstance().removePayment` | Refund flow | When payment needs to be removed | | `LinkRunner.getInstance().setPushToken` | Push notification setup | When FCM token is available | | `LinkRunner.getInstance().handleDeeplink` | Deep link entry points | When app is opened via a deep link | +| `LinkRunner.getInstance().setConsent` | App initialization or consent flow | Before `init`, and again when consent changes | | `LinkRunner.getInstance().setDisableAaidCollection` | App initialization or privacy settings | When you need to disable AAID collection | | `LinkRunner.getInstance().isAaidCollectionDisabled` | Privacy settings or compliance checks | When you need to check AAID collection status | diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index 9df2340..e39372e 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -62,6 +62,10 @@ To enable SKAdNetwork postback copies to be sent to Linkrunner, add the followin For complete SKAdNetwork integration details, see the [SKAdNetwork Integration Guide](/features/skadnetwork-integration). + + **Google Integrated Conversion Measurement (ICM)** is not available in the Capacitor plugin yet. `capacitor-linkrunner` 2.1.1 pins LinkrunnerKit 4.0.x and `io.linkrunner:android-sdk:4.0.1`, and ICM needs 4.1.0 on both. If you run Google App Campaigns for your iOS app, contact [support@linkrunner.io](mailto:support@linkrunner.io). See [Google ICM](/features/google-icm) for what it does. + + ### Step 5: 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. diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index dd08335..24362e2 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -83,6 +83,10 @@ For complete SKAdNetwork integration details, see the [SKAdNetwork Integration G For detailed Android backup configuration instructions, refer to the [Android SDK Backup Configuration](/sdk/android#backup-configuration). + + **Google Integrated Conversion Measurement (ICM)** is not available in the Cordova plugin yet. `cordova-linkrunner` 1.2.0 pins LinkrunnerKit 4.0.x and `io.linkrunner:android-sdk:4.0.1`, and ICM needs 4.1.0 on both. If you run Google App Campaigns for your iOS app, contact [support@linkrunner.io](mailto:support@linkrunner.io). See [Google ICM](/features/google-icm) for what it does. + + ## Initialization (Required) To initialize the Linkrunner SDK, add this code inside the `deviceready` event. No import or require is needed — `linkrunner` is globally available after plugin installation. diff --git a/sdk/expo.mdx b/sdk/expo.mdx index 1995021..d9b917b 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -85,6 +85,83 @@ For Android apps, the SDK provides backup rules to exclude Shared Preferences da For detailed backup configuration instructions, please refer to the [Android SDK Backup Configuration](/sdk/android/installation#backup-configuration). +### Google Integrated Conversion Measurement (Optional) + +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](/features/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](/features/google-icm#prerequisites). + + + + + + 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. Expo generates the `Podfile`, so add the pod through `expo-build-properties`: + + ```bash + npx expo install expo-build-properties + ``` + + ```json + { + "expo": { + "plugins": [ + [ + "expo-build-properties", + { + "ios": { + "extraPods": [{ "name": "GoogleAdsOnDeviceConversion" }] + } + } + ] + ] + } + } + ``` + + CocoaPods adds the `-ObjC` and `-lc++` linker flags for you, so there are no Xcode Build Settings to change. + + + If you manage `ios/` yourself instead of generating it, add `pod 'GoogleAdsOnDeviceConversion'` to `ios/Podfile` and run `pod install`. + + + + + ```bash + npx expo prebuild --clean + ``` + + ICM needs native code, so it does not work in Expo Go. Build a development client or run an EAS build. + + + + Set the values with `setConsent` before you call `init`: + + ```javascript + import linkrunner from "rn-linkrunner"; + + linkrunner.setConsent({ + isEEA: "granted", + hasConsentForDataUsage: "granted", + hasConsentForAdsPersonalization: "denied", + }); + + await linkrunner.init("YOUR_PROJECT_TOKEN"); + ``` + + See [Google Integrated Conversion Measurement](/sdk/react-native#google-integrated-conversion-measurement-optional) in the React Native guide for what each signal means and how to verify the setup. **For users outside the EEA, the UK, and Switzerland, report `isEEA` as denied and leave the other two unset.** See [Send Consent](/features/send-consent). + + + ## SDK Usage diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index 2bf97e1..0de8447 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -151,6 +151,93 @@ platform :ios, '15.0' For complete SKAdNetwork integration details, see the [SKAdNetwork Integration Guide](/features/skadnetwork-integration). +### Google Integrated Conversion Measurement (Optional) + +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](/features/google-icm) for how it works. + +Set this up if you run Google App Campaigns for your iOS app. Requires `linkrunner` 4.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](/features/google-icm#prerequisites). + + + + + + Already using the Firebase iOS SDK 11.14.0 or later? The `FirebaseAnalytics` pod brings this SDK in for you. Skip this step. + + + The plugin does not bundle this SDK, so apps that skip ICM carry none of its weight. Add it inside the `Runner` target in `ios/Podfile`: + + ```ruby + target 'Runner' 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 Xcode Build Settings to change. + + + + Set the values with `setConsent` before you call `init`, and again whenever the user changes their choice: + + ```dart + import 'package:linkrunner/linkrunner.dart'; + import 'package:linkrunner/models/lr_consent.dart'; + + await LinkRunner().setConsent( + LRConsent( + isEEA: ConsentStatus.GRANTED, + hasConsentForDataUsage: ConsentStatus.GRANTED, + hasConsentForAdsPersonalization: ConsentStatus.DENIED, + ), + ); + + await LinkRunner().init('YOUR_PROJECT_TOKEN'); + ``` + + Each signal takes `ConsentStatus.GRANTED`, `ConsentStatus.DENIED`, or `ConsentStatus.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. + + | Parameter | Meaning | + | --- | --- | + | `isEEA` | European regulations apply to this user (the EEA, the UK, or Switzerland) | + | `hasConsentForDataUsage` | The user agreed to their data being sent to Google for advertising | + | `hasConsentForAdsPersonalization` | The 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](/features/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 Dart API to call. Attribution comes back through [`getAttributionData`](#getting-attribution-data) 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` set to `true` 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) You'll need your [project token](https://dashboard.linkrunner.io/dashboard?m=documentation) to get started! @@ -949,6 +1036,7 @@ Connect APNs with Linkrunner | `LinkRunner().capturePayment` | Payment processing | When user makes a payment | | `LinkRunner().removePayment` | Refund flow | When payment needs to be removed | | `LinkRunner().handleDeeplink` | Deep link entry points | When app is opened via a deep link | +| `LinkRunner().setConsent` | App initialization or consent flow | Before `init`, and again when consent changes | | `LinkRunner().setDisableAaidCollection` | App initialization or privacy settings | When you need to disable AAID collection | | `LinkRunner().isAaidCollectionDisabled` | Privacy settings or compliance checks | When you need to check AAID collection status | diff --git a/sdk/ios.mdx b/sdk/ios.mdx index 94af2ef..3edbf2d 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -39,7 +39,7 @@ Alternatively, you can add the package dependency to your `Package.swift` file: ```swift dependencies: [ - .package(url: "https://github.com/linkrunner-labs/linkrunner-ios.git", from: "3.12.0") + .package(url: "https://github.com/linkrunner-labs/linkrunner-ios.git", from: "4.1.0") ] ``` @@ -96,6 +96,119 @@ For complete SKAdNetwork integration details, see the [SKAdNetwork Integration G The SDK requires network access to communicate with Linkrunner services. Make sure your app has the appropriate permissions for network access. +### Google Integrated Conversion Measurement (Optional) + +Integrated Conversion Measurement (ICM) recovers Google App Campaign installs 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](/features/google-icm) for how it works. + +Set this up if you run Google App Campaigns for your iOS app. Requires LinkrunnerKit 4.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](/features/google-icm#prerequisites). + + + + + + Already using the Firebase iOS SDK 11.14.0 or later? The `FirebaseAnalytics` pod brings this SDK in for you. Skip to the next step. + + + Linkrunner does not bundle this SDK, so apps that skip ICM carry none of its weight. Add it to your app yourself. + + + + Add `https://github.com/googleads/google-ads-on-device-conversion-ios-sdk` in **File** → **Add Package Dependencies...** and select the `GoogleAdsOnDeviceConversion` product. + + + Add the pod inside your app target in the `Podfile`: + + ```ruby + target 'YourApp' do + # ...your existing pods + + pod 'GoogleAdsOnDeviceConversion' + end + ``` + + Then run `pod install`. + + + + + + + Using CocoaPods? Skip this step. CocoaPods adds `-ObjC` and `-lc++` for you when it links Google's static framework. The one exception is an app target whose **Other Linker Flags** no longer contain `$(inherited)`, in which case add `-ObjC` yourself. + + + If you added Google's SDK with Swift Package Manager, add this to **Build Settings** → **Other Linker Flags** on your app target: + + ``` + -ObjC + ``` + + + Without it, ICM silently does nothing and your build still succeeds. Linkrunner finds Google's class through the Objective-C runtime, so nothing references it at link time, and the linker drops it from Google's static library. + + + + + Google reads consent signals when it matches your installs. See [Consent](#consent) below. + + + +That is the whole integration. Linkrunner detects the SDK at runtime and there is no API to call. Attribution comes back through [`getAttributionData`](#getting-attribution-data) as usual. + + + ODM matches on the time of first launch, which Linkrunner records when you call `initialize`. + + + + ICM complements SKAdNetwork, it does not replace it. Keep your existing [SKAdNetwork integration](/features/skadnetwork-integration) in place. + + +#### Consent + +Google needs to know whether European regulations apply to a user and what that user agreed to. Set the values with `setConsent` before you call `initialize`, and again whenever the user changes their choice. + +```swift +LinkrunnerSDK.shared.setConsent( + LinkrunnerConsent( + isEEA: .granted, + hasConsentForDataUsage: .granted, + hasConsentForAdsPersonalization: .denied + ) +) + +await LinkrunnerSDK.shared.initialize(token: "YOUR_PROJECT_TOKEN") +``` + +| Parameter | Meaning | +| --- | --- | +| `isEEA` | European regulations apply to this user (the EEA, the UK, or Switzerland) | +| `hasConsentForDataUsage` | The user agreed to their data being sent to Google for advertising | +| `hasConsentForAdsPersonalization` | The user agreed to their data being used to personalize ads | + +Each takes `.granted`, `.denied`, or `.unknown`. Anything left `.unknown` is omitted rather than reported as a denial, so Linkrunner never reports a choice your user did not make. + +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](/features/send-consent). App Tracking Transparency is not a substitute, because it governs IDFA access only and is reported separately. + + + Consent is stored between launches. Call `setConsent` again whenever the user's consent state changes, otherwise the previous value keeps being sent after your user has withdrawn it. + + +#### Verifying your setup + +Initialize with `debug: true` 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. On Swift Package Manager, check the `-ObjC` flag first, because a missing flag strips the class with no build warning. + ## Initialization (Required) Initialize the Linkrunner SDK in your app's startup code, typically in your `AppDelegate` or `SceneDelegate`: @@ -803,6 +916,7 @@ Connect APNs with Linkrunner | `LinkrunnerSDK.shared.removePayment` | Refund flow | When payment needs to be removed | | `LinkrunnerSDK.shared.setPushToken` | Push notification setup | When APNs token is available | | `LinkrunnerSDK.shared.handleDeeplink` | SceneDelegate deep link entry points | When app is opened via a deep link | +| `LinkrunnerSDK.shared.setConsent` | App initialization or consent flow | Before `initialize`, and again when consent changes | ## Complete Example diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index e9720f1..deedddc 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -76,6 +76,90 @@ If you are upgrading from an earlier version, the SDK will transparently migrate 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](https://docs.expo.dev/develop/development-builds/introduction/) to get started. +### Google Integrated Conversion Measurement (Optional) + +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](/features/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](/features/google-icm#prerequisites). + + + + + + 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. + + + + 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. + + | Parameter | Meaning | + | --- | --- | + | `isEEA` | European regulations apply to this user (the EEA, the UK, or Switzerland) | + | `hasConsentForDataUsage` | The user agreed to their data being sent to Google for advertising | + | `hasConsentForAdsPersonalization` | The 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](/features/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`](#getting-attribution-data) 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: @@ -745,6 +829,7 @@ Connect APNs with Linkrunner | `linkrunner.removePayment` | Refund flow | When payment needs to be removed | | `linkrunner.setPushToken` | Push notification setup | When FCM/APNs token is available | | `linkrunner.handleDeeplink` | Deep link entry points | When app is opened via a deep link | +| `linkrunner.setConsent` | App initialization or consent flow | Before `init`, and again when consent changes | ## Next Steps diff --git a/sdk/unity.mdx b/sdk/unity.mdx index bcb483a..9724d4a 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -59,7 +59,7 @@ In your Unity project, create or edit the file `Assets/Plugins/Android/mainTempl ```gradle dependencies { - implementation 'io.linkrunner:android-sdk:3.6.0' + implementation 'io.linkrunner:android-sdk:4.1.0' } ``` @@ -111,8 +111,11 @@ import android.util.Log; import com.unity3d.player.UnityPlayer; +import io.linkrunner.sdk.LinkRunner; import io.linkrunner.sdk.LinkrunnerJava; import io.linkrunner.sdk.LinkRunnerCallback; +import io.linkrunner.sdk.models.ConsentStatus; +import io.linkrunner.sdk.models.LinkrunnerConsent; import io.linkrunner.sdk.models.request.UserDataRequest; import io.linkrunner.sdk.models.request.CapturePaymentRequest; import io.linkrunner.sdk.models.request.RemovePaymentRequest; @@ -390,6 +393,25 @@ public class LinkrunnerBridge { LinkrunnerJava.getInstance().setDisableAaidCollection(disabled); } + private static ConsentStatus toConsentStatus(String value) { + if (value == null) return ConsentStatus.UNKNOWN; + try { + return ConsentStatus.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + return ConsentStatus.UNKNOWN; + } + } + + public static void setConsent(String isEEA, String dataUsage, String adsPersonalization) { + LinkRunner.getInstance().setConsent( + new LinkrunnerConsent( + toConsentStatus(isEEA), + toConsentStatus(dataUsage), + toConsentStatus(adsPersonalization) + ) + ); + } + public static void handleDeeplink(String deeplinkUrl) { if (deeplinkUrl == null || deeplinkUrl.isEmpty()) { Log.d(TAG, "handleDeeplink called with null or empty URL, ignoring"); @@ -442,7 +464,7 @@ After building your Unity project for iOS and opening the generated Xcode projec ``` https://github.com/linkrunner-labs/linkrunner-ios.git ``` -3. Select the latest version (3.8.0+) +3. Select the latest version (4.1.0+) 4. Click **Add Package** and choose the **LinkrunnerKitStatic** library @@ -667,7 +689,8 @@ func linkrunnerCapturePayment(_ userId: UnsafePointer, await LinkrunnerSDK.shared.capturePayment( amount: amount, userId: uid, - paymentId: pid, + // LinkrunnerKit 4.x requires a payment id. An empty value is rejected by the SDK. + paymentId: pid ?? "", type: paymentType, status: paymentStatus, eventData: eventData.isEmpty ? nil : eventData @@ -710,6 +733,24 @@ func linkrunnerEnablePIIHashing(_ enabled: Bool) { LinkrunnerSDK.shared.enablePIIHashing(enabled) } +@_cdecl("_LinkrunnerSetConsent") +func linkrunnerSetConsent(_ isEEA: UnsafePointer?, + _ dataUsage: UnsafePointer?, + _ adsPersonalization: UnsafePointer?) { + func status(_ value: UnsafePointer?) -> ConsentStatus { + guard let raw = toOptionalString(value) else { return .unknown } + return ConsentStatus(rawValue: raw.lowercased()) ?? .unknown + } + + LinkrunnerSDK.shared.setConsent( + LinkrunnerConsent( + isEEA: status(isEEA), + hasConsentForDataUsage: status(dataUsage), + hasConsentForAdsPersonalization: status(adsPersonalization) + ) + ) +} + @_cdecl("_LinkrunnerHandleDeeplink") func linkrunnerHandleDeeplink(_ deeplinkUrl: UnsafePointer?) { let url = toOptionalString(deeplinkUrl) @@ -744,6 +785,80 @@ func linkrunnerHandleDeeplink(_ deeplinkUrl: UnsafePointer?) { Alternatively, you can automate this with a Unity `PostProcessBuild` script — see the [Automation Tips](#automating-ios-dependency-with-a-post-build-script) section. +### Google Integrated Conversion Measurement (Optional) + +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](/features/google-icm) for how it works. + +Set this up if you run Google App Campaigns for your iOS app. Requires LinkrunnerKit 4.1.0 or later and `io.linkrunner:android-sdk:4.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](/features/google-icm#prerequisites). + + + + + Linkrunner does not bundle this SDK, so apps that skip ICM carry none of its weight. In the generated Xcode project, select **File** → **Add Package Dependencies...**, enter: + + ``` + https://github.com/googleads/google-ads-on-device-conversion-ios-sdk + ``` + + Select the `GoogleAdsOnDeviceConversion` product and add it to the same target you added **LinkrunnerKitStatic** to (**UnityFramework**). + + Like the LinkrunnerKit dependency, this has to be re-added each time Unity regenerates the Xcode project. Handle it in the same post-build script. + + + + In **Build Settings** → **Other Linker Flags** on the target that links Google's SDK (**UnityFramework**), add: + + ``` + -ObjC + ``` + + + Without it, ICM silently does nothing and your build still succeeds. Linkrunner finds Google's class through the Objective-C runtime, so nothing references it at link time, and the linker drops it from Google's static library. `-lc++` is applied for you by Google's `Package.swift`. + + + + + Call `SetConsent` with your consent values before `Initialize`, and again whenever the user changes their choice: + + ```csharp + LinkrunnerSDK.SetConsent( + isEEA: "granted", + hasConsentForDataUsage: "granted", + hasConsentForAdsPersonalization: "denied" + ); + + LinkrunnerSDK.Initialize("YOUR_PROJECT_TOKEN"); + ``` + + Each signal takes `"granted"`, `"denied"`, or `"unknown"`. Anything left `"unknown"` is dropped from the payload rather than reported as a denial. `SetConsent` works on both iOS and Android, using the bridge methods added in the [Java bridge](#step-3-create-the-java-bridge) and [Swift bridge](#step-3-create-the-swift-bridge) above. + + | Parameter | Meaning | + | --- | --- | + | `isEEA` | European regulations apply to this user (the EEA, the UK, or Switzerland) | + | `hasConsentForDataUsage` | The user agreed to their data being sent to Google for advertising | + | `hasConsentForAdsPersonalization` | The 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](/features/send-consent). + + + +#### 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 the `-ObjC` flag first, because a missing flag strips the class with no build warning. + --- ## C# Wrapper @@ -798,6 +913,9 @@ public class LinkrunnerSDK : MonoBehaviour [DllImport("__Internal")] private static extern void _LinkrunnerEnablePIIHashing(bool enabled); [DllImport("__Internal")] + private static extern void _LinkrunnerSetConsent(string isEEA, string dataUsage, + string adsPersonalization); + [DllImport("__Internal")] private static extern void _LinkrunnerHandleDeeplink(string deeplinkUrl); #endif @@ -974,6 +1092,24 @@ public class LinkrunnerSDK : MonoBehaviour #endif } + /// + /// Report Google Ads consent. Each value is "granted", "denied", or "unknown". + /// Call before Initialize(), and again whenever the user's consent state changes. + /// + public static void SetConsent(string isEEA, string hasConsentForDataUsage, + string hasConsentForAdsPersonalization) + { +#if UNITY_ANDROID && !UNITY_EDITOR + Bridge.CallStatic("setConsent", isEEA ?? "unknown", + hasConsentForDataUsage ?? "unknown", hasConsentForAdsPersonalization ?? "unknown"); +#elif UNITY_IOS && !UNITY_EDITOR + _LinkrunnerSetConsent(isEEA, hasConsentForDataUsage, hasConsentForAdsPersonalization); +#else + Debug.Log("[Linkrunner] SetConsent: " + isEEA + ", " + hasConsentForDataUsage + ", " + + hasConsentForAdsPersonalization); +#endif + } + /// /// Handle a deeplink for re-engagement attribution. /// Call this when the app is opened via a deeplink (cold start or warm start). @@ -1352,6 +1488,7 @@ LinkrunnerSDK.SetDisableAaidCollection(true); | `CapturePayment` | Payment flow | When payment succeeds | | `RemovePayment` | Refund flow | When payment is reversed | | `SetPushToken` | After token refresh | When push token changes | +| `SetConsent` | Before `Initialize` | At launch and whenever consent changes | | `HandleDeeplink` | Deeplink entry points | When app is opened via a deeplink | ---