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
// 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
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:
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
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:
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
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
// 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:
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:
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:
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:
// 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
)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, pluspriceandcurrencyfrom the product details. WithoutproductIdthe purchase is checked as a subscription and fails; without a price it's recorded at $0. Auto-capture adds both for products listed inoneTimeProductIds. 409means a one-time purchase isn't in the PURCHASED state yet (for example, still pending).- Other errors:
InfluToError.NotInitialized,Transport(no connection) andDecoding.
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:
val premium = entitlementActive || InfluTo.checkAccess().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 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()andclearAttribution()are plain functions. Callback overloads deliver results on a background thread. - Initialize once, in
Application.onCreateor with an app-wide scope — an activity'sonCreateruns again on every configuration change. - minSdk 24 and a Java 17 toolchain.
- Permissions: the core adds
INTERNET;android-sdk-billingaddscom.android.vending.BILLING. For the Play Data safety form, see Privacy & App Store. - Play Billing version:
android-sdk-billingexposes Play Billing Library 9.0.0, which Gradle resolves together with your own Billing dependency.