Android SDK

Complete reference for the AppRefer Android SDK. Native Kotlin library built on HttpURLConnection and the Google Play Install Referrer API. No OkHttp, no Retrofit, no heavy transitive dependencies.

Installation#

The SDK is distributed via JitPack. Add the JitPack repository (once per project) and the dependency:

settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven("https://jitpack.io")
    }
}
app/build.gradle.kts
dependencies {
    implementation("com.github.AppAgentic:apprefer-android-sdk:0.4.1")
}

Maven Central publish pending

JitPack works today with zero setup on your side — no accounts, no tokens. We plan to mirror to Maven Central under com.apprefer:apprefer-android-sdk once the Sonatype namespace is claimed. Track progress at github.com/AppAgentic/apprefer-android-sdk.

Permissions#

The library declares only INTERNET — nothing else is required in your AndroidManifest.xml. The SDK does not declare com.google.android.gms.permission.AD_ID so you do not have to make a Play Data Safety disclosure on its behalf.

Install Referrer#

Google Play Install Referrer is bundled transitively — you do not need to add com.android.installreferrer:installreferrer yourself. After a user clicks a trk.apprefer.com/api/c/<linkId> link and installs from Google Play, the referrer string is delivered automatically — the SDK parses gclid, fbclid, ar_click_id, and the utm_* parameters. No developer setup needed.

Google Advertising ID (optional)#

If play-services-ads-identifier is on your app's classpath, the SDK will read the GAID at configure() to improve deterministic matching. If you exclude it (for Play Data Safety simplicity), the SDK degrades gracefully — Install Referrer + IP/UA fingerprint still deliver attribution.

Quickstart#

Call AppRefer.configure() as early as possible in your app — typically in your first Activity.onCreate() or in an Application.onCreate() coroutine scope. The function is suspend; use lifecycleScope or any other CoroutineScope you control.

MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.apprefer.sdk.AppRefer
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            val attribution = AppRefer.configure(
                context = this@MainActivity,
                apiKey = "pk_live_your_key_here",
                debug = BuildConfig.DEBUG,
            )

            if (attribution != null) {
                Log.i("AppRefer", "Network: ${attribution.network}")
                Log.i("AppRefer", "Match type: ${attribution.matchType}")
            }
        }
    }
}

Java callback variant#

Not every Android host is Kotlin. Every suspend function has a @JvmOverloads callback overload so Java code can call it without any coroutine setup:

MainActivity.java
import com.apprefer.sdk.AppRefer;
import com.apprefer.sdk.AppReferCallback;
import com.apprefer.sdk.models.Attribution;

AppRefer.configure(
    this,
    "pk_live_your_key_here",
    /* userId */ null,
    /* debug */ false,
    /* logLevel */ 1,
    new AppReferCallback<Attribution>() {
        @Override public void onResult(Attribution attribution) {
            // handle attribution (nullable)
        }
        @Override public void onError(Throwable error) {
            // Won't normally fire — the SDK swallows failures by design.
        }
    }
);

API Reference#

The SDK never crashes the host app

Every public entry point is wrapped in runCatching / a safe-run helper. If called before configure(), methods return silently with a debug log. No throws, no crashes.

AppRefer.configure(context, apiKey, userId?, debug?, logLevel?)#

Initialize the SDK and resolve attribution. Call once at app launch. Calling configure() multiple times reuses the existing singleton state — only the first call performs a network request.

ParameterTypeDefaultDescription
contextContextrequiredAny Context — SDK stores applicationContext internally
apiKeyStringrequiredYour SDK key (pk_live_... or pk_test_...)
userIdString?nullOptional user ID to associate with attribution
debugBooleanfalseEnable verbose Logcat output
logLevelInt10 = none, 1 = info, 2 = debug — advanced tuning
Details
Signaturesuspend fun configure(context: Context, apiKey: String, userId: String? = null, debug: Boolean = false, logLevel: Int = 1): Attribution?
ReturnsAttribution? — the resolved attribution, or null for organic installs
ThrowsNever

AppRefer.trackEvent(eventName, properties?, revenue?, currency?)#

Send a custom event to AppRefer. Purchases are handled automatically via RevenueCat webhooks — use trackEvent for signup, tutorial completion, lead events, etc.

ParameterTypeDefaultDescription
eventNameStringrequiredEvent name (e.g., "signup", "tutorial_complete")
propertiesMap<String, Any?>?nullArbitrary key-value metadata
revenueDouble?nullRevenue amount (e.g., 9.99)
currencyString?nullISO 4217 currency code (e.g., "USD")
lifecycleScope.launch {
    AppRefer.trackEvent(
        eventName = "tutorial_complete",
        properties = mapOf("level" to 3, "time_spent" to 42),
    )

    // With revenue
    AppRefer.trackEvent(
        eventName = "purchase",
        revenue = 9.99,
        currency = "USD",
    )
}

AppRefer.setAdvancedMatching(email?, phone?, firstName?, lastName?, dateOfBirth?)#

Send hashed PII to improve ad network match rates. All values are SHA256-hashed on-device before being sent to the server. The server never receives plaintext PII.

ParameterTypeDefaultDescription
emailString?nullUser's email address
phoneString?nullPhone number in E.164 format
firstNameString?nullUser's first name
lastNameString?nullUser's last name
dateOfBirthString?nullDate of birth (YYYYMMDD)
lifecycleScope.launch {
    AppRefer.setAdvancedMatching(
        email = "user@example.com",
        phone = "+15551234567",
        firstName = "Jane",
        lastName = "Doe",
    )
}

AppRefer.setUserId(userId)#

Associate a user ID with the device's attribution. Typically called with the RevenueCat app user ID.

appreferId must be set on RevenueCat FIRST

Before any purchase can be attributed, you must set the appreferId subscriber attribute on RevenueCat. This is how AppRefer links RevenueCat webhook events back to the attributed device. Without it, purchase events from RevenueCat cannot be matched to attributions and will not be forwarded to ad networks.
lifecycleScope.launch {
    val attribution = AppRefer.configure(context, apiKey = "pk_live_...")

    // REQUIRED: link AppRefer device ID to RevenueCat
    AppRefer.getDeviceId()?.let { deviceId ->
        Purchases.sharedInstance.setAttributes(mapOf("appreferId" to deviceId))
    }

    // Optional: mirror RevenueCat user ID back to AppRefer
    AppRefer.setUserId(Purchases.sharedInstance.appUserID)
}

AppRefer.getAttribution()#

Returns the cached attribution from the last configure() call. Synchronous — no coroutine context required. Returns null if configure() has not completed or the device is organic.

Details
Signature@JvmStatic fun getAttribution(): Attribution?
ReturnsAttribution? — cached attribution result

AppRefer.getDeviceId()#

Returns the device ID generated by AppRefer. This is a stable UUID stored in the app's SharedPreferences, persisted across launches.

Details
Signature@JvmStatic fun getDeviceId(): String?
ReturnsString? — the device UUID, or null before configure()

Kill switch

The server-side kill switch ships with the SDK by default. If you disable an app in the AppRefer dashboard, the next configure() response turns the SDK off — all subsequent trackEvent calls become no-ops until you re-enable it. No code change required in the host app.

Attribution Model#

The Attribution data class returned by configure() contains the following properties:

PropertyTypeDescription
networkString?Ad network name (e.g., "meta", "google", "tiktok")
matchTypeString?Confidence level of the attribution match
campaignNameString?Campaign name from the tracking link
adsetNameString?Ad set / ad group name
adNameString?Ad creative name
fbclidString?Facebook click ID
gclidString?Google click ID
ttclidString?TikTok click ID
clickIdString?Generic click ID (AppRefer internal)
deviceIdString?Device UUID assigned by AppRefer
createdAtString?ISO 8601 timestamp of the attribution

Full Integration Example#

This example shows a complete integration with RevenueCat, including attribution resolution, user ID linking, and advanced matching after signup:

MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.apprefer.sdk.AppRefer
import com.revenuecat.purchases.Purchases
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Configure RevenueCat
        Purchases.configure(PurchasesConfiguration.Builder(this, "rc_public_key").build())

        lifecycleScope.launch {
            // 1. Initialize AppRefer and resolve attribution
            val attribution = AppRefer.configure(
                context = this@MainActivity,
                apiKey = "pk_live_your_key_here",
            )
            Log.i("AppRefer", "Network: ${attribution?.network ?: "organic"}")

            // 2. CRITICAL: link AppRefer device ID to RevenueCat
            //    Without this, purchases CANNOT be attributed to ad campaigns.
            AppRefer.getDeviceId()?.let { deviceId ->
                Purchases.sharedInstance.setAttributes(
                    mapOf("appreferId" to deviceId)
                )
            }

            // 3. Mirror the RevenueCat user ID back to AppRefer
            AppRefer.setUserId(Purchases.sharedInstance.appUserID)
        }
    }

    // Later, after the user signs up...
    fun onUserSignup(email: String, firstName: String) {
        lifecycleScope.launch {
            // 4. Advanced matching (hashed on-device)
            AppRefer.setAdvancedMatching(email = email, firstName = firstName)

            // 5. Track the signup event
            AppRefer.trackEvent("signup")
        }
    }
}

Best Practices#

  • Call configure() as early as possible in the app lifecycle (first Activity's onCreate() or the Application class). The sooner you call it, the more accurate the attribution window.
  • Always set appreferId on RevenueCat immediately after configure(). This is the primary mechanism for matching RevenueCat purchase webhooks back to AppRefer attributions. Without it, purchases cannot be attributed.
  • Run configure() inside lifecycleScope.launch or any coroutine scope you control. Do not call it on Dispatchers.Main — the SDK already hops to Dispatchers.IO for network work.
  • Optionally call setUserId() with the RevenueCat user ID for additional matching. This supplements appreferId but is not a replacement for it.
  • Use pk_test_ keys during development. Sandbox events are completely isolated from production and never forwarded to ad networks.
  • Enable debug = true in development to see verbose Logcat output. Disable in release builds.

Requirements#

RequirementMinimum Version
AndroidAPI 21 (Android 5.0 Lollipop)
Kotlin1.9+
JDK toolchain17
AGP8.0+
Dependencieskotlinx.coroutines, com.android.installreferrer (bundled). No OkHttp, no Retrofit, no Moshi.

Source code

The Android SDK is open source. Browse the repository at github.com/AppAgentic/apprefer-android-sdk.