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

# iOS SDK

> Complete guide for integrating Linkrunner in native iOS apps

## Requirements

* iOS 15.0 or higher
* Swift 5.9 or higher
* Xcode 14.0 or higher

## Installation

### Swift Package Manager

The Linkrunner SDK can be installed via Swift Package Manager (SPM), which is integrated directly into Xcode.

1. In Xcode, select **File** → **Add Package Dependencies...**
2. Enter the following repository URL:
   ```
   https://github.com/linkrunner-labs/linkrunner-ios.git
   ```
3. Select the version you want to use (we recommend using the latest version)
4. Click **Add Package**
5. Choose the library type **LinkrunnerStatic**

Alternatively, you can add the package dependency to your `Package.swift` file:

```swift theme={null}
dependencies: [
    .package(url: "https://github.com/linkrunner-labs/linkrunner-ios.git", from: "3.0.2")
]
```

And add the dependency to your target:

```swift theme={null}
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "Linkrunner", package: "linkrunner-ios")
        ]
    )
]
```

### Importing in Swift

After installation, you can import the SDK in your Swift files:

```swift theme={null}
import Linkrunner
```

### Required Permissions

#### App Tracking Transparency

If you plan to use IDFA (Identifier for Advertisers), you need to request permission from the user through App Tracking Transparency. Add the following to your `Info.plist` file:

```xml theme={null}
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads and improve your app experience.</string>
```

#### SKAdNetwork Configuration

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

```xml theme={null}
<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](/sdk/skadnetwork-integration).

#### Network Access

The SDK requires network access to communicate with Linkrunner services. Make sure your app has the appropriate permissions for network access.

## Initialization (Required)

Initialize the Linkrunner SDK in your app's startup code, typically in your `AppDelegate` or `SceneDelegate`:

You can find your project token [here](https://dashboard.linkrunner.io/dashboard?s=members\&m=documentation).

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

```swift theme={null}
import Linkrunner
import SwiftUI

@main
struct MyApp: App {
    init() {
        Task {
            do {
                try await LinkrunnerSDK.shared.initialize(
                    token: "YOUR_PROJECT_TOKEN",
                    secretKey: "YOUR_SECRET_KEY", // Optional: Required for SDK signing
                    keyId: "YOUR_KEY_ID", // Optional: Required for SDK signing
                    debug: true // Optional: Enable debug mode for development (defaults to false)
                )
                print("Linkrunner initialized successfully")
            } catch {
                print("Error initializing Linkrunner:", error)
            }
        }
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
```

## 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
* **`disableIdfa`** (optional): Boolean flag to disable IDFA collection (defaults to false)
* **`debug`** (optional): Boolean flag to enable debug mode for development (defaults to false)

You can find your project token, secret key, and key ID [here](https://dashboard.linkrunner.io/settings?s=sdk-signing).

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

* [Mixpanel - ID Management & User Identification](https://docs.mixpanel.com/docs/tracking-methods/id-management/identifying-users-simplified)
* [PostHog - How User Identification Works](https://posthog.com/docs/product-analytics/identify#how-identify-works)
* [Amplitude - Identify Users Documentation](https://amplitude.com/docs/get-started/identify-users)

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

* mixpanelDistinctId for Mixpanel
* amplitudeDeviceId for Amplitude
* posthogDistinctId for PostHog

```swift theme={null}
func onSignup() async {
    do {
        let userData = UserData(
            id: "123", // Required: User ID
            name: "John Doe", // Optional
            phone: "9876543210", // Optional
            email: "user@example.com", // Optional
            isFirstTimeUser: isFirstTimeUser,
            userCreatedAt: "2022-01-01T00:00:00Z", // Optional
            mixpanelDistinctId: "mixpanelDistinctId", // Optional - Mixpanel Distinct ID
            amplitudeDeviceId: "amplitudeDeviceId", // Optional - Amplitude Device ID
            posthogDistinctId: "posthogDistinctId" // Optional - PostHog Distinct ID
        )

        try await LinkrunnerSDK.shared.signup(
            userData: userData,
            additionalData: [:] // Optional: Any additional data
        )

        print("Signup successful")
    } catch {
        print("Error during signup:", error)
    }
}
```

## Handle Deeplink

To enable [remarketing and reattribution](/features/remarketing), 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.

Add the following to your `SceneDelegate.swift`:

```swift theme={null}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    // Cold start via Universal Link
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        // Check if app was launched via a Universal Link
        if let userActivity = connectionOptions.userActivities.first,
           userActivity.activityType == NSUserActivityTypeBrowsingWeb,
           let url = userActivity.webpageURL {
            Task {
                await LinkrunnerSDK.shared.handleDeeplink(url: url.absoluteString)
            }
        }

        // Check if app was launched via a custom URL scheme
        if let urlContext = connectionOptions.urlContexts.first {
            Task {
                await LinkrunnerSDK.shared.handleDeeplink(url: urlContext.url.absoluteString)
            }
        }
    }

    // Warm start — Universal Links (app already running in background)
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
              let url = userActivity.webpageURL else { return }

        Task {
            await LinkrunnerSDK.shared.handleDeeplink(url: url.absoluteString)
        }
    }

    // Warm start — Custom URL schemes (app already running in background)
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else { return }

        Task {
            await LinkrunnerSDK.shared.handleDeeplink(url: url.absoluteString)
        }
    }
}
```

## Getting Attribution Data

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

```swift theme={null}
func getAttributionInfo() async {
    do {
        let attributionData = try await LinkrunnerSDK.shared.getAttributionData()
        print("Attribution data:", attributionData)

        // Attribution data includes:
        // - deeplink: The deep link URL that led to app installation
        // - campaignData: Campaign information
        if let deeplink = attributionData.deeplink {
            print("Deeplink:", deeplink)
            // Handle the deeplink in your app
        }
    } catch {
        print("Error getting attribution data:", error)
    }
}
```

```swift theme={null}
struct AttributionData: Codable, Sendable {
    let deeplink: String?
    let campaignData: CampaignData
    let attributionSource: String
}

struct CampaignData: Codable, Sendable {
    let id: String
    let name: String
    let adNetwork: String?
    let type: String
    let installedAt: String
    let storeClickAt: String?
    let groupName: String
    let assetName: String
    let assetGroupName: String
}
```

## Setting User Data

Call `setUserData` each time the app opens and the user is logged in:

<Note>
  **`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.
</Note>

```swift theme={null}
func setUserData() async {
    do {
        let userData = UserData(
            id: "123", // Required: User ID
            name: "John Doe", // Optional
            phone: "9876543210", // Optional
            email: "user@example.com" // Optional
        )

        try await LinkrunnerSDK.shared.setUserData(userData)
        print("User data set successfully")
    } catch {
        print("Error setting user data:", error)
    }
}
```

## Setting CleverTap ID

Use `setAdditionalData` to add CleverTap ID to the SDK:

```swift theme={null}
func setAdditionalData() async {
    do {
        let integrationData = IntegrationData(
            clevertapId:clevertapId
        )

        try await LinkrunnerSDK.shared.setAdditionalData(integrationData)
        print("CleverTap ID set successfully")
    } catch {
        print("Error setting cleverTap ID:", error)
    }
}
```

## Revenue Tracking

<Note>
  Revenue data is only stored and displayed for attributed users. Make sure you have implemented the [`.signup`](#user-registration) function before capturing payments. To attribute a test user, follow the [Integration Testing](/testing/integration-testing) guide. You can verify your events are being captured on the [Events Settings](https://dashboard.linkrunner.io/dashboard/settings/events) page.
</Note>

### Capturing Payments

Track payment information:

```swift theme={null}
func capturePayment() async {
    do {
        try await LinkrunnerSDK.shared.capturePayment(
            amount: 99.99, // Payment amount
            userId: "user123", // User identifier
            paymentId: "payment456", // Optional: Unique payment identifier
            type: .firstPayment, // optional
            status: .completed, // optional
            eventData: [        // Optional: Ecommerce/custom event data
                "content_ids": ["product_123"],
                "content_type": "product",
                "currency": "USD",
                "value": 99.99,
                "num_items": 1,
                "order_id": "order_12345",
                "contents": [
                    [
                        "id": "product_123",
                        "quantity": 1,
                        "item_price": 99.99
                    ]
                ]
            ]
        )
        print("Payment captured successfully")
    } catch {
        print("Error capturing payment:", error)
    }
}
```

#### Available Payment Types

```swift theme={null}
public enum PaymentType: String, Sendable {
    case firstPayment = "FIRST_PAYMENT"
    case secondPayment = "SECOND_PAYMENT"
    case walletTopup = "WALLET_TOPUP"
    case fundsWithdrawal = "FUNDS_WITHDRAWAL"
    case subscriptionCreated = "SUBSCRIPTION_CREATED"
    case subscriptionRenewed = "SUBSCRIPTION_RENEWED"
    case oneTime = "ONE_TIME"
    case recurring = "RECURRING"
    case `default` = "DEFAULT"
}
```

#### Available Payment Statuses

```swift theme={null}
public enum PaymentStatus: String, Sendable {
    case initiated = "PAYMENT_INITIATED"
    case completed = "PAYMENT_COMPLETED"
    case failed = "PAYMENT_FAILED"
    case cancelled = "PAYMENT_CANCELLED"
}
```

### Removing Payments

Remove payment records (for refunds or cancellations):

```swift theme={null}
func removePayment() async {
    do {
        try await LinkrunnerSDK.shared.removePayment(
            userId: "user123", // User identifier
            paymentId: "payment456" // Optional: Unique payment identifier
        )
        print("Payment removed successfully")
    } catch {
        print("Error removing payment:", error)
    }
}
```

## Tracking Custom Events

<Note>
  Events are only stored and displayed for attributed users. Make sure you have implemented the [`.signup`](#user-registration) function before tracking events. To attribute a test user, follow the [Integration Testing](/testing/integration-testing) guide. You can verify your events are being captured on the [Events Settings](https://dashboard.linkrunner.io/dashboard/settings/events) page. For capturing revenue, it is recommended to use the [`.capturePayment`](#revenue-tracking) method instead of `.trackEvent`.
</Note>

Track custom events in your app:

```swift theme={null}
func trackEvent() async {
    do {
        try await LinkrunnerSDK.shared.trackEvent(
            eventName: "purchase_initiated", // Event name
            eventData: [ // Optional: Event data
                "product_id": "12345",
                "category": "electronics",
                "amount": 99.99 // Include amount as a number for revenue sharing with ad networks like Google and Meta
            ]
        )
        print("Event tracked successfully")
    } catch {
        print("Error tracking event:", error)
    }
}
```

### Revenue Sharing with Ad Networks

To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions:

```swift theme={null}
func trackPurchaseEvent() async {
    do {
        try await LinkrunnerSDK.shared.trackEvent(
            eventName: "purchase_completed",
            eventData: [
                "product_id": "12345",
                "category": "electronics",
                "amount": 149.99 // Revenue amount as a number
            ]
        )
        print("Purchase event with revenue tracked successfully")
    } catch {
        print("Error tracking purchase event:", error)
    }
}
```

<Note>
  For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number (Double
  or Int), not as a string.
</Note>

## Ecommerce Events

> **Minimum SDK Version:** Ecommerce Event Manager requires `linkrunner-ios` **v3.8.0** or above. Please ensure your SDK is updated before using this feature.

If you are tracking Ecommerce events to sync with Meta Catalog Sales, you must format your `eventData` to include Meta's 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](/ecommerce-manager/meta-commerce-manager#understanding-event_data).

### Add To Cart Example

Use the `trackEvent` method to send an `AddToCart` event:

```swift theme={null}
func trackAddToCart() async {
    do {
        try await LinkrunnerSDK.shared.trackEvent(
            eventName: "add_to_cart", // Map this custom event to "AddToCart" in the Linkrunner Dashboard
            eventData: [
                "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
            ]
        )
        print("Add To Cart event tracked successfully")
    } catch {
        print("Error tracking Add To Cart event:", error)
    }
}
```

### View Content Example

Use the `trackEvent` method to send a `ViewContent` event:

```swift theme={null}
func trackViewContent() async {
    do {
        try await LinkrunnerSDK.shared.trackEvent(
            eventName: "view_item", // Map this custom event to "ViewContent" in the Linkrunner Dashboard
            eventData: [
                "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
            ]
        )
        print("View Content event tracked successfully")
    } catch {
        print("Error tracking View Content event:", error)
    }
}
```

### Payment / Purchase Example

Use the `capturePayment` method to send a `Purchase` event containing the ecommerce payload:

```swift theme={null}
func capturePurchase() async {
    do {
        try await LinkrunnerSDK.shared.capturePayment(
            amount: 49.99,
            userId: "user123",
            paymentId: "payment_456",
            type: .firstPayment, // Map this payment type to "Purchase" in the Linkrunner Dashboard
            status: .completed,
            eventData: [
                "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,
                "order_id": "order_abc123" // Required for Purchase events
            ]
        )
        print("Purchase captured successfully")
    } catch {
        print("Error capturing purchase:", error)
    }
}
```

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

## Enhanced Privacy Controls

The SDK offers options to enhance user privacy:

```swift theme={null}
// Enable PII (Personally Identifiable Information) hashing
LinkrunnerSDK.shared.enablePIIHashing(true)

// Check if PII hashing is enabled
let isHashingEnabled = LinkrunnerSDK.shared.isPIIHashingEnabled()
```

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.

## Uninstall Tracking

### Before you begin

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

**Requirements:**

* iOS SDK 3.4.0 and later
* [Registering your app with APNs](https://developer.apple.com/documentation/usernotifications/registering-your-app-with-apns)

### iOS

Connect APNs with Linkrunner

<AccordionGroup>
  <Accordion title="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](https://developer.apple.com/account).
    * 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).
  </Accordion>

  <Accordion title="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.

           <img src="https://mintcdn.com/linkrunner-01ef8e08/dA0-VzMmaaAmaYW8/images/uninstall-tracking-dashboard-ios.png?fit=max&auto=format&n=dA0-VzMmaaAmaYW8&q=85&s=e44e679c08127f94bc524f89f5e889cb" alt="Uninstall Tracking" width="3000" height="1676" data-path="images/uninstall-tracking-dashboard-ios.png" />
  </Accordion>

  <Accordion title="Integrate with Linkrunner SDK">
    Follow these instructions to integrate APNs with the Linkrunner SDK:

    1. **Set up Push Notifications:**

    Enable push notifications in your Xcode project by adding the Push Notifications capability.

    2. Configure your app to provide the device's APNs token to the Linkrunner SDK.

    ```swift theme={null}
    import UIKit
    import Linkrunner

    class AppDelegate: UIResponder, UIApplicationDelegate {

        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            // Request push notification permissions
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
                if granted {
                    DispatchQueue.main.async {
                        application.registerForRemoteNotifications()
                    }
                }
            }
            return true
        }

        // Called when APNs token is received
        func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
            let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
            Task {
                try? await LinkrunnerSDK.shared.setPushToken(tokenString)
            }
        }

        func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
            print("Failed to register for remote notifications: \(error)")
        }
    }
    ```

    For SwiftUI apps using the new app lifecycle:

    ```swift theme={null}
    import SwiftUI
    import Linkrunner

    @main
    struct MyApp: App {
        @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }

    class AppDelegate: NSObject, UIApplicationDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
                if granted {
                    DispatchQueue.main.async {
                        application.registerForRemoteNotifications()
                    }
                }
            }
            return true
        }

        func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
            let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
            Task {
                try? await LinkrunnerSDK.shared.setPushToken(tokenString)
            }
        }
    }
    ```
  </Accordion>
</AccordionGroup>

## Function Placement Guide

| Function                                  | Where to Place                        | When to Call                                   |
| ----------------------------------------- | ------------------------------------- | ---------------------------------------------- |
| `LinkrunnerSDK.shared.initialize`         | App initialization                    | Once when app starts                           |
| `LinkrunnerSDK.shared.getAttributionData` | Attribution data handling flow        | Whenever the attribution data is needed        |
| `LinkrunnerSDK.shared.setAdditionalData`  | Integration code                      | When third-party integration IDs are available |
| `LinkrunnerSDK.shared.signup`             | Identification flow (signup or login) | Once when the user is identified               |
| `LinkrunnerSDK.shared.setUserData`        | Authentication logic                  | Every time app opens with logged-in user       |
| `LinkrunnerSDK.shared.trackEvent`         | Throughout app                        | When specific user actions occur               |
| `LinkrunnerSDK.shared.capturePayment`     | Payment processing                    | When user makes a payment                      |
| `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             |

## Complete Example

Here's a simplified example showing how to integrate Linkrunner in a SwiftUI iOS app:

You can find your project token [here](https://dashboard.linkrunner.io/dashboard?s=members\&m=documentation).

```swift theme={null}
import SwiftUI
import Linkrunner

@main
struct MyApp: App {
    init() {
        Task {
            await initializeLinkrunner()
        }
    }

    func initializeLinkrunner() async {
        do {
            try await LinkrunnerSDK.shared.initialize(token: "YOUR_PROJECT_TOKEN")
            print("Linkrunner initialized successfully")
        } catch {
            print("Error initializing Linkrunner:", error)
        }
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                Text("Linkrunner Demo")
                    .font(.largeTitle)

                Button("Track Event") {
                    Task {
                        await trackCustomEvent()
                    }
                }
                .buttonStyle(.borderedProminent)
            }
            .padding()
        }
    }

    func trackCustomEvent() async {
        do {
            try await LinkrunnerSDK.shared.trackEvent(
                eventName: "button_clicked",
                eventData: ["screen": "home"]
            )
            print("Event tracked successfully")
        } catch {
            print("Error tracking event:", error)
        }
    }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Test Your Integration" icon="flask" href="/testing/integration-testing">
    Validate your setup end-to-end
  </Card>

  <Card title="Set Up Deep Linking" icon="link" href="/features/deep-linking">
    Configure deep links for your app
  </Card>
</CardGroup>

## Support

If you encounter issues during integration, contact us at [support@linkrunner.io](mailto:support@linkrunner.io).
