Developer Docs
AffRef Mobile SDK
Affiliate attribution for iOS and Android apps via Universal Links and App Links. No IDFA. No paste prompts. No SKAdNetwork paperwork. Free.
Mobile subscription apps have two real options for tracking which affiliate referred each paying user: pay Branch or AppsFlyer $1,000+ a month, or wire up Universal Links yourself. The AffRef SDK is the second option in three steps.
How it works
- An affiliate shares
https://affref.com/go/your-brand/THEIRCODE. - User taps the link on a device where your app is installed. iOS / Android see the link host matches a verified associated domain → open your app directly with the URL.
- Your app forwards the activity into
AffRef.handle(userActivity:)(iOS) orAffRef.handle(uri)(Android). - The SDK extracts the affiliate code, fires the
onCodeCapturedcallback, and reports the attribution to AffRef. - Your code forwards the captured code to RevenueCat / Adapty / Qonversion as a user attribute.
- When the user subscribes, the subscription webhook fires AffRef. AffRef reads the
affref_codeattribute and credits the conversion.
If the user doesn't have your app installed when they tap the link, the affref.com URL opens an interstitial in their browser that redirects to the App Store / Play Store. After install, AffRef's IP-fingerprint matching credits the install — that's a separate (lossier) path from this SDK and runs without any code on your side.
SDK is currently in private beta.
The SDK lives in private GitHub repos while we're working with the first few merchants. Tap the chat widget in the bottom-right (or sign in to the merchant dashboard and message us from there) and we'll grant your GitHub account access to the iOS and Android repos, plus a step-by-step setup guide. Everything described below is real and shipping; only the install path is gated.
iOS
Install
Swift Package Manager. Once you have beta access, you'll get a Git URL to add in Xcode → File → Add Packages.
Minimum iOS 13.
1. Add the Associated Domains capability
In Xcode: target → Signing & Capabilities → + Capability → Associated Domains. Add:
applinks:affref.com
Then in AffRef, on your brand's Mobile SDK page, paste your Apple Team ID and Bundle ID. AffRef hosts the matching apple-app-site-association file at the link domain — there's nothing for you to upload or sign.
2. Start the SDK and forward Universal Links
import SwiftUI
import AffRef
@main
struct MyApp: App {
init() {
AffRef.start(with: .init(publishableKey: "pk_live_xxx"))
AffRef.onCodeCaptured { code in
Purchases.shared.attribution.setAttributes(["affref_code": code])
}
}
var body: some Scene {
WindowGroup {
ContentView()
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
AffRef.handle(userActivity: activity)
}
.onOpenURL { url in
// SwiftUI delivers Universal Links via .onOpenURL on cold
// launch and .onContinueUserActivity on warm. Wire both.
AffRef.handle(url: url)
}
}
}
}
For UIKit apps, call AffRef.handle(userActivity:) from application(_:continue:restorationHandler:) in your AppDelegate.
3. Block the paywall briefly on first launch
AffRef.awaitReady(timeout: 1.5) { _ in
showPaywall()
}
Universal-Link captures are synchronous, but blocking once is cheap insurance against the install-and-subscribe-in-3-seconds race.
Android
Install
Once your GitHub account has access to the private repo, add it as a local Gradle module or a Git submodule. Public JitPack / Maven Central distribution is coming after we exit beta. Minimum Android 5.0 (API 21).
1. Declare AffRef as an App Link
In AndroidManifest.xml, on whichever activity should receive affiliate-link taps:
<activity android:name=".MainActivity" android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="affref.com" android:pathPrefix="/r/" />
<data android:scheme="https" android:host="affref.com" android:pathPrefix="/go/" />
</intent-filter>
</activity>
In AffRef on your brand's Mobile SDK page, paste your package name + release-keystore SHA256 fingerprint. AffRef serves the matching assetlinks.json at the link domain. Android verifies it on install when autoVerify="true" is set.
2. Start the SDK and forward App Links
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AffRef.start(this, AffRef.Config(publishableKey = "pk_live_xxx"))
AffRef.onCodeCaptured { code ->
Purchases.sharedInstance.setAttributes(mapOf("affref_code" to code))
}
}
}
// In your launcher activity:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { AffRef.handle(it) }
}
3. Block the paywall briefly on first launch
AffRef.awaitReady(timeoutMs = 1500) showPaywall()
Subscription SDK adapters
The SDK is subscription-agnostic. Forward the captured code as a user attribute on whichever stack you use.
RevenueCat
// iOS
Purchases.shared.attribution.setAttributes(["affref_code": code])
// Android
Purchases.sharedInstance.setAttributes(mapOf("affref_code" to code))
Adapty
// iOS
Adapty.updateProfile(.init(customAttributes: ["affref_code": code]))
// Android
val params = AdaptyProfileParameters.Builder()
.withCustomAttributes(mapOf("affref_code" to code)).build()
Adapty.updateProfile(params) { }
Qonversion
// iOS
Qonversion.shared().setUserProperty(.custom("affref_code"), value: code)
// Android (Kotlin)
Qonversion.shared.setCustomUserProperty("affref_code", code)
Raw StoreKit / Play Billing
No SDK in the middle? Set the code on your own backend at purchase time, then POST it to AffRef's conversions endpoint from your server-side receipt validator.
Public API
// iOS
AffRef.start(with: .init(publishableKey: "pk_..."))
AffRef.onCodeCaptured { code in }
AffRef.currentCode // String?
AffRef.handle(userActivity:) // Universal Links
AffRef.handle(url:) // Custom URL scheme
AffRef.awaitReady(timeout:) // Block briefly before paywall
AffRef.setCode("ABC") // Manual code entry
AffRef.clear()
// Android
AffRef.start(context, AffRef.Config(publishableKey = "pk_..."))
AffRef.onCodeCaptured { code -> }
AffRef.currentCode() // String?
AffRef.handle(uri) // App Links
AffRef.awaitReady(timeoutMs) // Block briefly before paywall
AffRef.setCode("ABC") // Manual code entry
AffRef.clear()
Privacy
- No IDFA. No App Tracking Transparency prompt.
- No GAID on Android.
- No clipboard reads. No "Pasted from Safari" prompts.
- Device ID is a UUID generated locally and stored in UserDefaults / SharedPreferences. App-scoped, not cross-app.
- No SKAdNetwork, no MMP, no third parties in the data path.
FAQ
Why Universal Links and App Links, not SKAdNetwork or fingerprinting?
Universal Links and App Links are Apple's and Google's blessed mechanism for opening an installed app from a web URL. They're deterministic (no probabilistic matching), require no user prompt, and don't expose any device identifier. SKAdNetwork is designed for paid ad networks and gives you lossy aggregated data. Fingerprinting is on the path to deprecation across both platforms. Universal / App Links are the only attribution mechanism that's likely to still work in 5 years.
What if the user doesn't have my app installed when they tap the link?
The affref.com URL opens an interstitial in their browser that redirects to the App Store (iOS) or Play Store (Android). After install and first open, AffRef runs IP-fingerprint matching server-side to credit the install — accuracy ~50-70%, no SDK code involved. The SDK takes over again once the user taps an affiliate link a second time and your app handles it via Universal / App Links directly (typically ~99% accurate).
Does this work for free apps with no subscription?
Yes. The SDK captures the code; you decide what counts as a "conversion" (signup, purchase, anything). Fire AffRef's /conversions endpoint from your backend when the event happens.
Can I also support promo codes that users type into my app?
Yes. Add an in-app text field and call AffRef.setCode("THECODE") when the user submits. The SDK validates the code against your active affiliates server-side and credits the conversion exactly like a Universal-Link capture.
How do I get a publishable key?
Sign up for AffRef, create a Mobile brand, and your pk_live_… key appears on the Mobile SDK integration page along with your team-ID / package-name inputs.
Troubleshooting
iOS: tapping the smart link opens Safari instead of my app
iOS didn't recognise the URL as a Universal Link. Common causes, in order:
- AASA file at
affref.com/.well-known/apple-app-site-associationdoesn't list your{team-id}.{bundle-id}. The Mobile SDK integration page's Test panel shows this status in real time. Save your Team ID + Bundle ID on the Mobile SDK page if not yet. - iOS caches AASA per-app-install. After updating Team ID, delete and reinstall the app to force a fresh AASA fetch.
- Sometimes iOS shows an "Open in YourApp" banner at the top of Safari instead of auto-opening. Tap the banner — that's the same as a Universal Link tap.
- Associated Domains capability needs the paid Apple Developer Program. Free Personal Team accounts can't add it.
Android: tapping the smart link opens Chrome instead of my app
App Link verification failed. Common causes:
- Wrong SHA256 fingerprint. Most common failure. You submitted your debug-keystore fingerprint, not your release keystore. App Links verify against the signing cert of the installed APK. Get the release fingerprint from Play Console → Setup → App signing, or run
keytool -list -v -keystore release.keystore -alias your-alias. autoVerify="true"missing from your intent-filter.- Some OEMs (Samsung in particular) silently fail autoVerify. Run
adb shell pm get-app-links your.package.nameto inspect verification state.
The app opens but onCodeCaptured never fires
SwiftUI delivers Universal Links via .onContinueUserActivity when the app is warm-launched and via .onOpenURL on cold launch. Wire both modifiers (shown in the iOS install snippet above). If you only wire one, you'll silently lose ~50% of captures.
Conversion isn't crediting even though installs are
The SDK captured the code on install but the conversion webhook didn't reference it. Two checks:
- In your
onCodeCapturedcallback, are you actually callingsetAttributes(["affref_code": code])on your subscription SDK? The SDK only captures; you have to forward the code to RevenueCat / Adapty / Qonversion. - Is the user subscribing before the attribute is set? On a fast cold-launch-then-subscribe flow this race can lose ~10% of conversions. Call
AffRef.awaitReady(timeout: 1.5)before showing your paywall. - Is your RevenueCat webhook firing events at AffRef? Check the Live Test panel on your Mobile SDK page — it shows last RC event timestamp.
Sandbox / TestFlight events polluting my live conversions
By default AffRef drops events where event.environment === 'SANDBOX'. If you're explicitly testing and want them to record, toggle Drop sandbox events off in section 5 of the Mobile SDK page. Sandbox conversions always land as pending, never auto-approved.