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

  1. An affiliate shares https://affref.com/go/your-brand/THEIRCODE.
  2. 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.
  3. Your app forwards the activity into AffRef.handle(userActivity:) (iOS) or AffRef.handle(uri) (Android).
  4. The SDK extracts the affiliate code, fires the onCodeCaptured callback, and reports the attribution to AffRef.
  5. Your code forwards the captured code to RevenueCat / Adapty / Qonversion as a user attribute.
  6. When the user subscribes, the subscription webhook fires AffRef. AffRef reads the affref_code attribute 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.

Apple Developer Program required. The Associated Domains capability is gated behind the paid Apple Developer Program ($99/yr). Free Personal Team accounts can't add it. If you're shipping to the App Store you already have this; if not, enroll at developer.apple.com/programs.

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-association doesn'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.name to 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 onCodeCaptured callback, are you actually calling setAttributes(["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.

Ready to grow your affiliate program?

Start your 7-day free trial. Cancel anytime.

View Plans
Usually replies within an hour
Hey 👋 I'm Aaron, founder of AffRef. Drop your email and your question and I'll get back to you fast.
By chatting you agree to our privacy policy.
End this chat? History on this device clears.