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
// 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
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:
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
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:
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
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
// 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:
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():
// 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:
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:
let access = await InfluTo.checkAccess()
let premium = entitlementActive || access.hasAccesscheckAccess(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, thencheckAttribution. - Local state lives in the
com.influto.sdkUserDefaultssuite and survives app updates; deleting the app removes it.