Skip to content
InfluToDocs

Install the SDK

Add InfluTo to an iOS app

Install the InfluTo Swift package, detect referred installs, accept typed codes, and report StoreKit 2 purchases or tag RevenueCat subscribers.

The InfluTo Swift package adds creator attribution to a native iOS app built with SwiftUI or UIKit. It tells you whether an install came from a creator's link or code, lets people type a creator's code, and gets each purchase to InfluTo — through RevenueCat, or straight from StoreKit 2. Plan for an afternoon; most of it goes into the purchase side.

Package Swift package https://github.com/influto/influto-ios, library and module InfluTo
Latest version 1.1.1
Registry Swift Package Manager (Swift Package Index)
Requires iOS 16+, Swift 5.9 (Xcode 15) or newer. Also builds for macOS 13, tvOS 16 and watchOS 9
Dependencies None — Foundation, StoreKit 2 and CryptoKit only
Source github.com/influto/influto-ios (MIT)

Install

Swift
// Xcode → File → Add Package Dependencies →
//   https://github.com/influto/influto-ios   (from 1.1.1)
// Or in Package.swift:
.package(url: "https://github.com/influto/influto-ios.git", from: "1.1.1")

Add the InfluTo library to your app target. The SDK ships through Swift Package Manager only; there is no CocoaPods pod.

Initialize

Swift
import InfluTo

// Call once at launch
var config = InfluToConfig(apiKey: "YOUR_API_KEY", debug: true)
// RevenueCat apps: hand the SDK a setter so it can tag the subscriber
// config.revenueCatAttributeSetter = { attrs in Purchases.shared.attribution.setAttributes(attrs) }
try await InfluTo.initialize(config)

Every SDK method is async. Call initialize once at launch, from a Task, and wait for it before calling anything else. It throws when it fails — a wrong key, no network — so catch the error, log it, and carry on; the next launch tries again. A typical startup, in order:

MyApp.swift
import SwiftUI
import InfluTo
import RevenueCat // only if you use RevenueCat

@main
struct MyApp: App {
    init() {
        Purchases.configure(withAPIKey: "appl_…") // RevenueCat apps: configure it first
        Task { await startInfluTo() }
    }
    var body: some Scene { WindowGroup { ContentView() } }
}

func startInfluTo() async {
    var config = InfluToConfig(
        apiKey: Bundle.main.object(forInfoDictionaryKey: "INFLUTO_API_KEY") as? String ?? "",
        appVersion: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
    )
    config.revenueCatAttributeSetter = { attrs in
        Purchases.shared.attribution.setAttributes(attrs)
    }
    do {
        try await InfluTo.initialize(config)
    } catch {
        print("InfluTo initialize failed: \(error)") // retried on the next launch
        return
    }
    let attribution = await InfluTo.checkAttribution()
    await InfluTo.identifyUser(Purchases.shared.appUserID)
}

In UIKit, start the same Task from application(_:didFinishLaunchingWithOptions:).

The API key starts with it_ and is on Setup & SDK; one key works for development and production. Keep it out of committed source — for example in an .xcconfig file that isn't checked in, exposed to the app through Info.plist as above.

Parameter Type Default What it does
apiKey String required The app's SDK key (it_…)
debug Bool false Prints [InfluTo] lines
appVersion String? nil (sent as "unknown") Your app version, sent with the startup call
autoCapture Bool true Store-direct apps: observe StoreKit 2 transactions and report them
revenueCatAttributeSetter (@Sendable ([String: String]) -> Void)? nil RevenueCat apps: how the SDK sets the subscriber attributes

Detect referred users

Swift
let attribution = await InfluTo.checkAttribution()
if attribution.attributed {
  // User came from a referral — show a special offer.
  print("Referred by \(attribution.referralCode ?? "")")
}

AttributionResult has these properties:

Property Meaning
attributed true when the install came from a creator's link or code
referralCode The creator's code, uppercase
attributionMethod "ip_device_match" (link-click matching) or "manual_entry" (a code the user applied)
clickedAt When the link was clicked, or the code applied
message A short explanation, for logs

Attribution is decided once per install and stored on the device; later calls return the stored answer without a request. Only a failed request is retried, on the next call. A reinstall starts over.

checkAttribution never throws. On a network or server error — or if initialize hasn't succeeded — it returns attributed == false and stores nothing. Link-click matching only counts taps from the last 72 hours — see attribution. To show referred users a better offer, see Reward referred users.

Identify the user

Call identifyUser as soon as you know who the user is, with the same ID your purchases are keyed on — for RevenueCat, the app user ID:

Swift
await InfluTo.identifyUser(Purchases.shared.appUserID)

It matters for three things: InfluTo uses it to credit a RevenueCat purchase when the influto_code attribute didn't make it, checkAccess uses it for free-access codes, and reportPurchase sends it by default. Call it once per sign-in, ideally in the user's first session.

Track events

Swift
await InfluTo.trackEvent(TrackEventOptions(
    eventType: "paywall_viewed",
    appUserId: userID,
    properties: ["placement": AnyCodable("onboarding")]
))

Events show on Real-Time Events and help you read the funnel. They never create conversions or commission — only validated purchases do. Every call gets an event ID, so an accidental double call counts once. For trial_started, subscription_purchased and subscription_renewed the ID is derived from the event, user and properties, so the same event counts once even across launches; include something that changes per occurrence (a transaction ID, say) in properties when the event can repeat. For your own once-only events, pass eventId.

Let users type a code

Swift
// Validate + apply a code the user typed
let result = await InfluTo.applyCode("FITGURU30")
if result.valid && result.applied == true {
  // Unlock the offer
}

// Or drop in the SwiftUI field: InfluToReferralCodeInput(onApplied: { result in … })
// Free-access codes (after identifyUser): setReferralCode(code).grantsAccess == true
// → unlock premium, and gate premium on (await InfluTo.checkAccess()).hasAccess.

applyCode(_:appUserId:) validates the code and applies it if it's valid; the result's applied tells you whether it was applied. Codes are trimmed and uppercased for you. An invalid code comes back with valid == false and an errorCode: INVALID_FORMAT, CODE_NOT_FOUND, CODE_EXPIRED or NETWORK_ERROR. If you build your own field, write your own copy: message and influencer can contain the creator's personal name.

The ready-made field is the SwiftUI view InfluToReferralCodeInput. It validates as the user types and applies the code when they tap the button:

Swift
InfluToReferralCodeInput(appUserId: userID, onApplied: { result in
    // the code is applied — unlock the offer
})
Parameter Default Notes
appUserId identified user The user to bind the code to; free-access codes need one
autoPrefill / autoValidate true / false Pre-fill the code from a link-attributed install, and validate it right away
showCampaignName / showReferrerName false / false Show a creator's name only with their consent
title, labels — Heading and translated texts (placeholder, apply, valid, invalid)
onValidated, onApplied — Called after each validation, and after the code is applied

In UIKit, host it in a UIHostingController. More on the UX: Let users enter a referral code. All parameters: UI components.

Purchases

InfluTo only pays commission on purchases it has verified with the store. Choose exactly one path per platform — see Choose a validation path.

RevenueCat

Pass a revenueCatAttributeSetter, as in the startup example above. When checkAttribution finds a referral, or a code is applied, the SDK calls it with influto_code (the code) and influto_referral (the string "true"). Then connect the webhook — see RevenueCat.

Store-direct

StoreKit 2 capture is built in. When the app is set to store-direct in Settings → Validation and autoCapture is true, initialize starts observing Transaction.updates and back-fills your current entitlements and unfinished transactions. Each transaction is reported once, keyed on its original transaction ID. The SDK never calls finish() — your app still finishes its transactions. You can also drive it yourself with startPurchaseObservation(), stopPurchaseObservation() and syncExistingPurchases().

To report a purchase as soon as it happens, pass the verification result from product.purchase():

Swift
// Store-direct only (no RevenueCat). Report right after a purchase — StoreKit
// doesn't send purchases made in your app to the background listener, and
// reports are de-duplicated, so this is safe alongside auto-capture:
if case .success(let verification) = try await product.purchase() {
  let r = try await InfluTo.reportPurchase(verification: verification)
}

InfluTo de-duplicates repeated reports of the same transaction, so a purchase reported both here and by auto-capture still counts once. reportPurchase(jws:) takes a raw StoreKit 2 signed transaction instead. Both throw:

Swift
do {
    _ = try await InfluTo.reportPurchase(verification: verification)
} catch InfluToError.retryable(_) {
    // 503: the exchange rate was briefly unavailable — try again later
} catch InfluToError.notConfigured(_) {
    // 400: iOS isn't set to store-direct yet
} catch {
    print("InfluTo reportPurchase failed: \(error)")
}

Optionally, tie the transaction to your user ID with StoreKit's account token: product.purchase(options: [.appAccountToken(InfluTo.appAccountToken(forUserID: userID))]). It's the same UUID every time for a given user ID.

Set-up, credentials, the Sandbox/Production environment setting and testing: Store-direct.

Free-access codes

Free-access codes give a user premium without a purchase. Redeem one with setReferralCode(_:appUserId:) after identifyUser, or pass the user ID — a code with no user is refused. The result tells you what happened: grantsAccess, freeAccess, entitlement and expiresAt. applyCode doesn't return these flags; after it, result.campaign?.campaignType == "FREE_ACCESS" tells you the code was a free-access code.

Gate premium on your store entitlement or InfluTo's answer:

Swift
let access = await InfluTo.checkAccess()
let premium = entitlementActive || access.hasAccess

checkAccess(appUserId:) uses the identified user when you don't pass one, caches a positive answer for 5 minutes (across launches too), never caches a negative one, and returns hasAccess == false on errors. See Free access codes.

Verify

After the first successful initialize, Setup & SDK shows "Integrated — SDK connected" with the platform and SDK version it last saw. With debug: true the SDK prints each step, and Real-Time Events lists your events and purchases. For the full end-to-end test and the integration status checklist, see Verify the integration.

To test attribution again on the same device, clearAttribution() forgets the stored attribution, code and user ID. It's for testing only — deleting and reinstalling the app gives a cleaner start.

iOS notes

  • Privacy manifest included. The package ships PrivacyInfo.xcprivacy: no tracking, no tracking domains, and the data types it collects (not linked to identity). It doesn't use the advertising identifier, so no App Tracking Transparency prompt is needed. Details for your App Privacy label: Privacy & App Store.
  • StoreKit 2 only. Store-direct reporting needs the signed transaction (JWS); never send the unsigned JSON representation. The SDK sends the JWS even when on-device verification fails, and InfluTo verifies it on the server.
  • Sandbox vs Production. TestFlight and sandbox purchases only verify when the app's Apple environment is set to Sandbox in Settings → Validation.
  • RevenueCat order: configure RevenueCat, then initialize, then checkAttribution.
  • Local state lives in the com.influto.sdk UserDefaults suite and survives app updates; deleting the app removes it.