Get started

iOS SDK

Swift, callable from Objective-C. No dependencies. Includes a privacy manifest. Pro plan and up.

Requirements

  • iOS 13+.
  • Swift or Objective-C.
  • An SDK key: in the dashboard, open your app → Settings → SDK keys → New key. It's shown once, so copy it then.

Install

In Xcode, choose File → Add Package Dependencies…, paste the repository address, choose Up to Next Major Version from 0.3.3, and add the Unolink library to your app target.

https://github.com/unolink-dev/unolink-sdk

Unolink installs through Swift Package Manager, not CocoaPods. If your project also uses CocoaPods for other libraries and pod install fails with an encoding error (Ruby 4, or a locale that isn't UTF-8), run it in UTF-8:

LANG=en_US.UTF-8 pod install

Configure

Call configure once, as early as possible: in your @main App's init(), or in application(_:didFinishLaunchingWithOptions:).

import Unolink

@main
struct MyApp: App {
    init() {
        #if DEBUG
        Unolink.configure(apiKey: "ulk_…", options: UnolinkOptions(debug: true))
        #else
        Unolink.configure(apiKey: "ulk_…")
        #endif
    }
    var body: some Scene { WindowGroup { ContentView() } }
}

A compiled library can't see your build settings, so pass debug: true from debug builds yourself, as above. That marks the install as a test device.

Objective-C:

@import Unolink;

[Unolink configureWithApiKey:@"ulk_…" options:[UNLOptions new]];

Options

OptionDefaultWhat it does
debugfalseMarks this install as a test device. Dashboards hide test devices unless Include test devices is on.
logLevel.warnHow much goes to the Xcode console, prefix [Unolink]: .none, .error, .warn, .info, .debug.
baseURLhttps://api.unolink.devThe SDK API. Leave it as it is.
Unolink.configure(apiKey: "ulk_…", options: UnolinkOptions(logLevel: .debug, debug: true))

The key is public: it ships inside your app and can only report data for that one app. See SDK keys.

Funnel events

Add one line at each moment that matters. These become the steps in Funnel and Journeys.

Unolink.onboardingCompleted()                       // the last onboarding screen is done
Unolink.paywallViewed(placement: "onboarding")      // a paywall appeared; placement says which one
Unolink.paywallSkipped(placement: "onboarding")     // closed without buying
Unolink.signUp(method: "apple")                     // an account was created
Unolink.signUpSkipped()                             // went on as a guest (0.3.2+)

Without RevenueCat, report trials and purchases yourself. The amount is a Decimal, so prices never round:

Unolink.trialStarted(productId: "pro_yearly")
Unolink.purchase(amount: product.price, currency: "USD", productId: product.id)
Unolink.purchaseCancelled(productId: product.id)    // closed the purchase sheet
Unolink.purchaseFailed(productId: product.id, reason: "payment_failed")

Your own events take a name and up to 20 properties:

Unolink.track("level_up", properties: ["level": 3])

Objective-C:

[Unolink paywallViewedWithPlacement:@"onboarding" properties:nil];
[Unolink purchaseWithAmount:[NSDecimalNumber decimalNumberWithString:@"49.99"] currency:@"USD" productId:nil properties:nil];

The full list, with the names that reach the dashboard, is in Events.

User ID and RevenueCat

If you use RevenueCat, connect it under your app → Settings → RevenueCat. Then don't call purchase or trialStarted: RevenueCat sends trials, purchases, renewals, cancellations and refunds itself, and Unolink ignores app purchase calls so nothing counts twice.

To join those purchases to the install, report RevenueCat's own app user ID. With SDK 0.3.2 or later, one line keeps it current. Call it once at launch, before or after configure:

Unolink.connectRevenueCat { Purchases.isConfigured ? Purchases.shared.appUserID : nil }

Unolink reads the ID at launch, whenever the app becomes active and before each event, and sends it only when it changed. After Purchases.logIn and Purchases.logOut, call Unolink.revenueCatUserChanged() to send the new ID at once. Return nil while RevenueCat isn't configured: reading Purchases.shared before that stops the app.

Without it (or before 0.3.2), call setUserId with RevenueCat's ID after Purchases.configure, after every Purchases.logIn and Purchases.logOut, and on every launch. Use one or the other, not both:

// after Purchases.configure, logIn and logOut, and on every launch
Unolink.setUserId(Purchases.shared.appUserID)

For a guest, that's RevenueCat's anonymous ID, so a purchase made before signing up joins the install right away. After logIn, it's the account's ID. Every ID an install has reported counts, so the guest's purchase stays joined. See Guests and accounts.

Unolink never stores the ID itself, only a one-way hash that's different for each of your apps.

Deferred deep links are Android only. Apple gives apps no install referrer, so your app can't be told which link was tapped, and Unolink never fingerprints devices to guess. Unolink.onDeferredDeepLink(_:) exists so code can be shared across platforms, but on iPhone it isn't called today.

In the dashboard, iPhone installs are matched to the link click they came from, without fingerprinting. The match isn't sent to the app. Turn it off per app in its Settings.

Your links still send iPhone visitors straight to the App Store and count their clicks, and the SDK still counts every install and event.

Other calls

CallWhat it does
Unolink.installIdThe random ID of this install, or nil before configure. It identifies no person or device.
Unolink.flush()Sends queued events now. Normally not needed.
Unolink.setEnabled(false)Turns tracking off and deletes queued events, for your opt-out setting. true turns it back on.

How it behaves

  • First launch: creates a random install ID in Application Support (excluded from iCloud backup, never the Keychain), so a reinstall or a new phone is a new install.
  • Automatic events: first_open, session_start (on launch, or after 30 minutes in the background) and app_background. They're free.
  • Events are stored on the device (up to 1,000) and sent in batches every 15 seconds while the app is active, and when it goes to the background. Offline, they wait and retry.
  • It never blocks the main thread and never throws.

Test devices

Configure with debug: true in debug builds, as above. Their data is real but hidden until you turn on Include test devices in Funnel and Journeys. More in Testing.

Privacy

  • No IDFA, no App Tracking Transparency prompt, no fingerprinting.
  • The bundled privacy manifest declares product interaction (your events) and purchase history (purchases you report), used for analytics, not linked to identity, and no tracking. Use the same answers on your App Store privacy label.
  • A random install ID, app version, OS version, SDK version, phone or tablet, language. Country comes from the network edge. IP addresses are never stored.
  • Never put names, emails or other personal data in event properties.

Troubleshooting

  • Nothing in the dashboard? Turn on Include test devices: debug builds with debug: true are hidden by default.
  • Still nothing? Configure with UnolinkOptions(logLevel: .debug, debug: true) and search the Xcode console for [Unolink]. A wrong or revoked key is logged as an error.
  • Some iPhone installs show no link? Expected: not every iPhone install can be matched to a click, and those are shown on their own row. See Deep links on iPhone.

More in Troubleshooting.