Skip to content
InfluToDocs

Install the SDK

Add InfluTo to an Android app

Add the InfluTo Android SDK from Maven Central, detect referred installs, accept typed codes, and connect RevenueCat or Google Play purchases.

The InfluTo Android SDK adds creator attribution to a native Android app written in Kotlin. 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 Google Play Billing. Plan for an afternoon; most of it goes into the purchase side.

Package to.influ:android-sdk, plus optional to.influ:android-sdk-ui (Compose) and to.influ:android-sdk-billing (Play Billing capture)
Latest version 1.1.1 (all three artifacts)
Registry Maven Central
Requires minSdk 24, Java 17. Built with Kotlin 2.2, compileSdk 35
Dependencies Core: kotlinx-coroutines only
Source github.com/influto/influto-android (MIT)

Install

Kotlin
// build.gradle.kts
dependencies {
    implementation("to.influ:android-sdk:1.1.1")
    // optional: Compose code field (InfluToReferralCodeInput)
    implementation("to.influ:android-sdk-ui:1.1.1")
    // optional: store-direct purchase auto-capture
    implementation("to.influ:android-sdk-billing:1.1.1")
}

The artifacts are on Maven Central, so mavenCentral() must be in your repositories. Add only what you need — each add-on brings the core SDK with it:

Artifact Add it when What it brings
android-sdk Always The SDK; adds the INTERNET permission
android-sdk-ui You want the ready-made Compose code field InfluToReferralCodeInput (Material 3)
android-sdk-billing Your app is store-direct (no RevenueCat) Automatic purchase capture. Adds Google Play Billing Library 9.0.0 (billing-ktx) to your build and the com.android.vending.BILLING permission

The core and billing artifacts ship their own R8/ProGuard rules; there is nothing to add.

Initialize

Kotlin
import to.influ.sdk.InfluTo
import to.influ.sdk.InfluToConfig

// Call once at startup. initialize() is a suspend function — launch it from a
// coroutine, or use the callback overload: InfluTo.initialize(context, config) { result -> }
lifecycleScope.launch {
  InfluTo.initialize(applicationContext, InfluToConfig(
    apiKey = "YOUR_API_KEY",
    // RevenueCat apps: optional explicit setter (otherwise found by reflection)
    // revenueCatAttributeSetter = { attrs -> Purchases.sharedInstance.setAttributes(attrs) },
  ))
}

initialize is a suspend function. Call it once at startup and wait for it before calling anything else — if it hasn't succeeded, checkAttribution and reportPurchase throw InfluToError.NotInitialized. 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.kt
class MyApp : Application() {
    private val appScope = MainScope()

    override fun onCreate() {
        super.onCreate()
        // RevenueCat apps: configure it first
        Purchases.configure(PurchasesConfiguration.Builder(this, "goog_…").build())

        appScope.launch {
            try {
                InfluTo.initialize(this@MyApp, InfluToConfig(
                    apiKey = BuildConfig.INFLUTO_API_KEY,
                    appVersion = BuildConfig.VERSION_NAME,
                ))
            } catch (e: Exception) {
                Log.w("InfluTo", "initialize failed", e) // retried on the next launch
                return@launch
            }
            val attribution = InfluTo.checkAttribution()
            InfluTo.identifyUser(Purchases.sharedInstance.appUserID)
        }
    }
}

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 a BuildConfig field filled from a Gradle property kept in local.properties or your CI secrets.

Not in a coroutine? Every network method also has a callback overload that takes (Result<T>) -> Unit, for example InfluTo.initialize(context, config) { result -> … }. Callbacks run on a background thread, so switch to the main thread before touching UI.

Parameter Type Default What it does
apiKey String required The app's SDK key (it_…)
debug Boolean false Logs to Logcat under the InfluTo tag
appVersion String? null (sent as "unknown") Your app version, sent with the startup call
autoCapture Boolean true Store-direct apps: start Play Billing capture when android-sdk-billing is on the classpath
revenueCatAttributeSetter ((Map<String, String>) -> Unit)? null RevenueCat apps: how the SDK sets subscriber attributes. If null, the SDK finds RevenueCat on its own
oneTimeProductIds Set<String> empty Your one-time and consumable product IDs, so captured purchases are validated as one-time purchases

Detect referred users

Kotlin
val attribution = InfluTo.checkAttribution()   // suspend
if (attribution.attributed) {
  // User came from a referral — show a special offer.
  Log.d("App", "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 is fail-soft: on a network or server error 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:

Kotlin
InfluTo.identifyUser(Purchases.sharedInstance.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

Kotlin
InfluTo.trackEvent(
    eventType = "paywall_viewed",
    appUserId = userId,
    properties = mapOf("placement" to "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 (an order ID, say) in properties when the event can repeat. For your own once-only events, pass eventId.

Let users type a code

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

// Or the Compose field (android-sdk-ui): InfluToReferralCodeInput(onApplied = { result -> … })
// Free-access codes (after identifyUser): setReferralCode(code).grantsAccess == true
// → unlock premium, and gate premium on InfluTo.checkAccess().hasAccess.

applyCode(code, 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, NETWORK_ERROR or UNKNOWN. 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 composable InfluToReferralCodeInput from android-sdk-ui. It validates as the user types and applies the code when they tap the button:

Kotlin
InfluToReferralCodeInput(
    appUserId = userId,
    onApplied = { result -> /* 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, placeholder, applyLabel, validMessage, invalidMessage — Texts, for translation
onValidated, onApplied, modifier — Callbacks and layout

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

There's nothing to wire in code: when checkAttribution finds a referral, or a code is applied, the SDK finds the RevenueCat SDK and sets influto_code (the code) and influto_referral (the string "true") on the subscriber. To be explicit, pass the setter yourself:

Kotlin
revenueCatAttributeSetter = { attrs -> Purchases.sharedInstance.setAttributes(attrs) },

Then connect the webhook — see RevenueCat. Don't add android-sdk-billing to a RevenueCat app; it isn't needed.

Store-direct

Add android-sdk-billing. When the app is set to store-direct in Settings → Validation and autoCapture is true, initialize starts an observer with its own Play Billing connection, which reports new purchases and sweeps existing ones each time the app starts. Each purchase is reported once, keyed on its purchase token; only purchases in the PURCHASED state are sent.

If your app has its own BillingClient (most do), also hand each purchase to the module from your PurchasesUpdatedListener so it's reported the moment it happens. It shares the same de-duplication, so nothing is reported twice:

Kotlin
override fun onPurchasesUpdated(result: BillingResult, purchases: MutableList<Purchase>?) {
    if (result.responseCode != BillingClient.BillingResponseCode.OK || purchases == null) return
    scope.launch {
        for (purchase in purchases) {
            InfluToBilling.report(context, purchase) // subscriptions
            // one-time products: InfluToBilling.report(context, purchase, oneTime = true, price = 4.99, currency = "EUR")
        }
    }
}

InfluToBilling also offers startObservation(context, oneTimeProductIds), stopObservation() and syncExistingPurchases(context).

To report purchases yourself with the core SDK instead, set autoCapture = false and call reportPurchase:

Kotlin
// Store-direct only (no RevenueCat). With the android-sdk-billing artifact on the
// classpath, purchases are captured AUTOMATICALLY. To report manually:
InfluTo.reportPurchase(
  purchaseToken = purchase.purchaseToken,
  // productId = "coins_100",  // one-time / consumable products only
)
Kotlin
try {
    InfluTo.reportPurchase(purchaseToken = purchase.purchaseToken)
} catch (e: InfluToError.Server) {
    if (e.retryable) { /* 503: exchange rate briefly unavailable — retry later */ }
    if (e.notConfigured) { /* 400: Android isn't set to store-direct yet */ }
}
  • One-time and consumable products need productId, plus price and currency from the product details. Without productId the purchase is checked as a subscription and fails; without a price it's recorded at $0. Auto-capture adds both for products listed in oneTimeProductIds.
  • 409 means a one-time purchase isn't in the PURCHASED state yet (for example, still pending).
  • Other errors: InfluToError.NotInitialized, Transport (no connection) and Decoding.

Set-up, the Play service account and testing: Store-direct.

Free-access codes

Free-access codes give a user premium without a purchase. Redeem one with setReferralCode(code, appUserId = userId) after identifyUser, or pass the user ID — a code with no user is refused. The SetCodeResult 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:

Kotlin
val premium = entitlementActive || InfluTo.checkAccess().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 logs each step under the InfluTo tag, 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 — clearing the app's storage or reinstalling gives a cleaner start.

Android notes

  • Coroutines first. Network methods are suspend; getReferralCode(), getPrefilledCode() and clearAttribution() are plain functions. Callback overloads deliver results on a background thread.
  • Initialize once, in Application.onCreate or with an app-wide scope — an activity's onCreate runs again on every configuration change.
  • minSdk 24 and a Java 17 toolchain.
  • Permissions: the core adds INTERNET; android-sdk-billing adds com.android.vending.BILLING. For the Play Data safety form, see Privacy & App Store.
  • Play Billing version: android-sdk-billing exposes Play Billing Library 9.0.0, which Gradle resolves together with your own Billing dependency.