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 native bridges and deep links for you:Then ask your agent to “integrate Linkrunner”. See Linkrunner Agent Skills.
Prerequisites
- Unity 2021.3 LTS or newer
- Android: Android 5.0+ (API 21), Gradle 8.0+
- iOS: iOS 15.0+, Xcode 14.0+, Swift 5.9+
- A Linkrunner project token — get it from Dashboard → Documentation
Architecture Overview
The integration uses Unity’s native plugin system:- Android: A Java bridge class calls
LinkrunnerJava(the SDK’s callback-based Java API) and communicates results back to Unity viaUnitySendMessage. - iOS: A Swift bridge file wraps the Linkrunner iOS SDK’s
asyncmethods into C-callable functions (via@_cdecl), which Unity calls through[DllImport("__Internal")]. - C#: A
LinkrunnerSDK.cswrapper provides a unified cross-platform API.
Android Setup
Step 1: Add the SDK Dependency
In your Unity project, create or edit the fileAssets/Plugins/Android/mainTemplate.gradle (or your custom Gradle template) and add:
Assets/Plugins/Android/settingsTemplate.gradle:
Step 2: Permissions
Add the following to yourAssets/Plugins/Android/AndroidManifest.xml:
AD_ID permission:
Step 3: Create the Java Bridge
CreateAssets/Plugins/Android/LinkrunnerBridge.java:
iOS Setup
Step 1: Add the SDK
After building your Unity project for iOS and opening the generated Xcode project:- In Xcode, select File → Add Package Dependencies…
- Enter the repository URL:
- Select the latest version (4.1.0+)
- Click Add Package and choose the LinkrunnerKitStatic library
Step 2: Info.plist Configuration
Add to yourInfo.plist (can also be done via Unity’s Assets/Plugins/iOS/Info.plist additions):
Step 3: Create the Swift Bridge
The Linkrunner iOS SDK is a pure Swift module — it does not inherit fromNSObject and has no @objc annotations, so it cannot be called directly from Objective-C. We use a Swift bridge file with @_cdecl to expose C-callable functions that Unity can invoke via [DllImport("__Internal")].
Create Assets/Plugins/iOS/LinkrunnerUnityBridge.swift:
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 for how it works. Set this up if you run Google App Campaigns for your iOS app. Requires LinkrunnerKit 4.1.0 or later andio.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.
1
Add Google's On-Device Measurement SDK (iOS)
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: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.2
Add the -ObjC linker flag
In Build Settings → Other Linker Flags on the target that links Google’s SDK (UnityFramework), add:
3
Report consent
Call Each signal takes
SetConsent with your consent values before Initialize, and again whenever the user changes their choice:"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 and Swift bridge above.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.Verifying your setup
Initialize with debug mode on and look for this line in the Xcode console: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
CreateAssets/Scripts/LinkrunnerSDK.cs in your Unity project:
Usage
Scene Setup
- Create an empty
GameObjectin your first scene - Name it
LinkrunnerCallbackHandler(must match exactly) - Attach the
LinkrunnerSDK.csscript to it
Initialization (Required)
CallInitialize as early as possible — typically in your startup scene:
User Identification (Required)
CallSignup 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.
Handle Deeplinks
CallHandleDeeplink when your app is opened via a deeplink — both cold start (app was closed) and warm start (app was in background):
deeplink from OnDeeplinkHandled as the resolved destination instead of the original tracking URL.
The
is_linkrunner field indicates whether the deeplink was created through Linkrunner. Use this to determine if you should apply Linkrunner-specific attribution logic.- Android
- iOS
Create a custom Update your
UnityPlayerActivity to capture deeplinks:AndroidManifest.xml to use this activity and add intent filters for your deeplink scheme.Set User Data on App Open
Call every time the app opens and the user is logged in:SetUserData is optional and is not a replacement for Signup. Always call Signup first as soon as the user is identified (signup or login). Use SetUserData afterwards only when additional user details become available later — for example, when the user adds a phone number, email, or completes their profile after identification.Get Attribution Data
OnAttributionData event. With the reference bridge code in this guide, the shape differs by platform.
iOS
Track Events
When tracking revenue-related events, the
amount field must be a number (not a string) for ad network revenue optimization to work correctly.Capture Payment
paymentId is required and must be unique for each payment. It is used for idempotent deduplication, so retries cannot double count revenue.
Payment types: FIRST_PAYMENT, SECOND_PAYMENT, WALLET_TOPUP, FUNDS_WITHDRAWAL, SUBSCRIPTION_CREATED, SUBSCRIPTION_RENEWED, ONE_TIME, RECURRING, DEFAULT
Payment statuses: PAYMENT_INITIATED, PAYMENT_COMPLETED, PAYMENT_FAILED, PAYMENT_CANCELLED
Remove Payment (Refunds)
Push Token
Privacy Controls
Function Placement Guide
Automating iOS Dependency with a Post-Build Script
Since Unity regenerates the Xcode project on each build, you can automate the SPM dependency addition using a Unity Editor script: CreateAssets/Editor/LinkrunnerPostBuild.cs:
Unity’s
PBXProject API does not natively support adding SPM packages. After the Xcode project is generated, you will need to add the LinkrunnerKit SPM dependency manually in Xcode, or use a third-party tool like unity-spm to automate it.Testing
- Enable debug mode:
LinkrunnerSDK.Initialize(token, debug: true) - Create a test campaign at Dashboard → Create Campaign
- Uninstall the app from your test device
- Click the campaign link on the device (opens in browser)
- Install and open the app build within 3 minutes
- Verify in the dashboard that click count, install count, and signup count increase
- Call
GetAttributionData()to confirm attribution is returned
Troubleshooting
Android
- ProGuard/R8 issues: Add to your ProGuard rules:
- Duplicate classes: If another plugin includes Kotlin stdlib, use Gradle’s
excludeto avoid conflicts.
iOS
- Swift file not compiling: Unity does not automatically compile
.swiftfiles. After generating the Xcode project, addLinkrunnerUnityBridge.swiftto the UnityFramework target’s Compile Sources build phase manually. - “Use of unresolved identifier ‘LinkrunnerSDK’”: The LinkrunnerKit SPM package hasn’t been added to the Xcode project. Add it via File → Add Package Dependencies.
- Bridging header issues: When Xcode prompts to create a bridging header after adding the first Swift file, accept it. Ensure
SWIFT_VERSIONis set to5.0or higher in UnityFramework build settings. - Linker errors: Ensure you selected LinkrunnerKitStatic (not the dynamic variant) when adding the SPM package.
- ATT dialog not showing: The
NSUserTrackingUsageDescriptionkey must be present in Info.plist. The SDK handles requesting permission automatically during initialization.
General
- Callbacks not received: Verify the
GameObjectis named exactlyLinkrunnerCallbackHandlerand has theLinkrunnerSDK.csscript attached. It must exist in the scene when native code callsUnitySendMessage. - Editor testing: All SDK calls are no-ops in the Unity Editor and log to the console instead. Test on actual Android/iOS devices.