Guides

Add device fingerprinting to an Android app (Kotlin)

Add device fingerprinting to an Android app in Kotlin: add the Maven dependency, identify at sign-in from a ViewModel, and verify on your server.

This tutorial adds device fingerprinting to a Kotlin Android app with the Fingerly Android SDK. It has one dependency, Kotlin coroutines, nothing from Google Play services, and two install-time permissions that need no prompt. The server recognises the device by a random install ID the SDK keeps, and your backend decides what the result means.

Before you start

  • Android 5.0 (API 21) or newer.
  • A public key for the development environment, and a secret key for your server.
  • A decision about consent: the SDK collects nothing until consent is GRANTED.

Add the SDK and verify the result

  1. Step 1: Create your keys

    In the Fingerly dashboard, open Integration > SDK keys and create a public key in the development environment, and a secret key in the same environment for your server. Development keys run the same detection as production and cost nothing. A production key must also name your app, an Android package or an Apple app ID, as in API keys and environments.

  2. Step 2: Add the Maven repository and the dependency

    The SDK is a compiled library on Fingerly's Maven repository. Add the repository where your project declares its repositories:

    settings.gradle.kts
    dependencyResolutionManagement {
        repositories {
            google()
            mavenCentral()
            maven { url = uri("https://maven.fingerly.io") }
        }
    }
    
    build.gradle.kts
    dependencies {
        implementation("io.fingerly:fingerly:0.2.0")
    }
    

    The SDK's manifest merges into yours. It declares INTERNET and ACCESS_NETWORK_STATE, and its R8 rules are bundled.

  3. Step 3: Load the client and identify at sign-in

    load and identify are suspend functions, and collection runs on Dispatchers.IO. Hold one client for the life of the process.

    SignInViewModel.kt
    import android.app.Application
    import androidx.lifecycle.AndroidViewModel
    import androidx.lifecycle.viewModelScope
    import io.fingerly.sdk.Fingerly
    import io.fingerly.sdk.core.ConsentState
    import io.fingerly.sdk.identify
    import kotlinx.coroutines.async
    import kotlinx.coroutines.launch
    
    class SignInViewModel(private val app: Application) : AndroidViewModel(app) {
        private val fingerly = viewModelScope.async {
            // consent = GRANTED only once you have consent (or another lawful basis)
            Fingerly.load(app, apiKey = "fly_pk_development_…", consent = ConsentState.GRANTED)
        }
    
        fun signIn(email: String, password: String) = viewModelScope.launch {
            val result = fingerly.await().identify(tag = "sign-in")
            api.signIn(email, password, requestId = result.requestId)
        }
    }
    

    If identification fails, carry on without a request ID and let your server treat it as missing evidence:

    SignIn.kt
    val requestId = runCatching { fingerly.identify(tag = "sign-in").requestId }.getOrNull()
    api.signIn(email, password, requestId)
    

    Using Java? FingerlyJava is the same client with callbacks in place of suspend functions. See the Android SDK page.

  4. Step 4: Verify on your server

    A phone is a device you do not control, so the decision is never made in the app. Send the requestId to your backend with the action, read the stored event with your secret key, and check it belongs to this action and is recent.

    Sign-in handler (Node.js)
    import { FingerlyAPIError, load } from '@fingerly/node'
    
    const fingerly = load({ secretKey: process.env.FINGERLY_SECRET_KEY! })
    
    export async function afterPasswordCheck(requestId: unknown) {
      if (typeof requestId !== 'string' || requestId === '') return 'second-factor' // missing evidence
    
      try {
        const event = await fingerly.events.get(requestId)
        if (event.tag !== 'sign-in') return 'second-factor'
        if (Date.now() - Date.parse(event.occurred_at) > 2 * 60 * 1000) return 'second-factor'
        return event.suspect_level === 'high' ? 'second-factor' : 'allow'
      } catch (error) {
        if (error instanceof FingerlyAPIError && error.code === 'event_not_found') return 'second-factor'
        throw error // still pending: answer with a retry, never a refusal
      }
    }
    

    Also check the request ID has not been used before, and answer a pending event with a retry rather than a refusal. Server-side verification explains each check, and the use-case recipes include a helper that does all of them.

  5. consent defaults to PENDING, which collects nothing. Load with PENDING and call setConsent(ConsentState.GRANTED) from your consent tool, or pass GRANTED as above once you have consent or another lawful basis. See consent tools.

  6. Step 6: Run it on a device

    Run the app, sign in, then open Identification > Events in the dashboard to see the visitor, score and signals. In an emulator the event records the android_emulator signal, which is expected there.

What the visitor ID survives

EventEffect on the visitor ID
The app is closed and reopened, or updatedUnchanged.
The app is uninstalled and installed againA new install ID and a new visitor ID. The record lives in noBackupFilesDir, so it is removed with the app and never restored from a backup.
Fingerly.deleteStoredIdentity(context) is calledThe next identification made with consent gets a new install ID and a new visitor ID.

Because a reinstall starts fresh, treat a new visitor ID on Android as a new install rather than proof of a new person. Link accounts to a visitor ID as evidence and not as an identity. See visitor identification.

Hardware-backed attestation

On Android 7.0 (API 24) and newer, the SDK has the device keystore attest to a one-time challenge from Fingerly, and the server verifies it. It works without configuration. To also have the server check the attestation names your app, list your package names and signing certificates on the key. See mobile app attestation.

Common questions

How do I get a unique device ID on Android?

Call identify() and read visitorId. It is a stable identifier Fingerly issues for the install, not an Android hardware identifier. The SDK never reads one such as ANDROID_ID.

Which permissions does the SDK need?

Two, INTERNET and ACCESS_NETWORK_STATE, both granted at install time without a prompt. It never requests QUERY_ALL_PACKAGES. See package visibility for the packages it checks for.

Does it need Google Play services?

No. The only dependency is Kotlin coroutines, and attestation needs no Google Play services, so it also works on devices without them.

What happens after a reinstall?

A reinstall gets a new visitor ID, because the install ID is removed with the app. Tell your server the difference between a new install and a new person by joining the visitor ID with what you already know.

Can I call it from Java?

Yes. FingerlyJava offers the same calls with callbacks, delivered on the main thread, and each call returns a Cancellation you can use when the screen goes away.