Android SDK

Kotlin-based SDK for Android applications with device fingerprinting, SIM swap detection, and behavioral analytics

Installation

Maven Central (Recommended)

Add the SDK to your build.gradle.kts file. The SDK is published to Maven Central, so ensure you have the repository configured:

kotlin
repositories {
    mavenCentral()
}

dependencies {
    implementation("com.keverd:sdk:1.0.0")
}

AAR File (Alternative)

If you prefer to use a local AAR file, download it and add it to your project's libs directory (typically located at app/libs/). Then add the following dependencies:

Note: The SDK requires several dependencies. When using the AAR file, you must include all required dependencies manually. The SDK uses Retrofit for networking, Kotlin Coroutines for asynchronous operations, and Gson for JSON serialization.

kotlin
dependencies {
    implementation(files("libs/keverd-sdk.aar"))
    
    // Required dependencies
    implementation("androidx.core:core-ktx:1.10.1")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("com.google.code.gson:gson:2.10.1")
}

Permissions

Add these permissions to your AndroidManifest.xml file. These permissions are required for the SDK to collect device and network information:

xml
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

READ_PHONE_STATE: Required for SIM card information collection. This is a runtime permission on Android 6.0+ (API level 23+). You must request it at runtime using ActivityCompat.requestPermissions() or a permission handling library. The SDK will gracefully handle cases where this permission is not granted, but SIM data will not be collected.

INTERNET: Required for network requests to the Keverd API. This is a normal permission and is automatically granted at install time.

ACCESS_NETWORK_STATE: Used to check network connectivity before making API requests. This is a normal permission and is automatically granted at install time.

Quick Start

1. Initialize the SDK

Initialize the SDK in your Application class. This ensures the SDK is initialized once when your app starts, following the singleton pattern. The SDK uses the application context internally to prevent memory leaks.

Important: The apiBaseUrl must use HTTPS. The SDK will throw an IllegalArgumentException if you provide an HTTP URL. The default API endpoint is https://api.keverd.com.

kotlin
import com.keverd.sdk.Config
import com.keverd.sdk.KeverdFingerprint

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        val config = Config(
            apiBaseUrl = "https://api.keverd.com",
            apiKey = "your-api-key-here",
            consentRequired = true
        )
        
        KeverdFingerprint.init(this, config)
    }
}

2. Request Consent (if required)

If consentRequired is set to true in your configuration (which is the default), you must obtain explicit user consent before data collection can proceed. This is important for GDPR, CCPA, and other privacy regulations.

How it works: When you call submit() without consent, the SDK will return Result.ConsentRequired. You should then show a consent dialog to the user. Once consent is granted, call setConsent(true) and then retry the submission.

kotlin
val sdk = KeverdFingerprint.init(context, config)

// Show consent dialog to user
showConsentDialog { granted ->
    sdk.setConsent(granted)
    
    if (granted) {
        submitFingerprint(userId)
    }
}

3. Submit Fingerprint

Submit fingerprint data and receive a risk score. The submit() method is a suspend function, so it must be called from a coroutine scope (typically lifecycleScope in Activities or viewModelScope in ViewModels).

Threading: Data collection and network calls run on Dispatchers.IO to avoid blocking the main thread. The callback (onResult) is automatically switched to Dispatchers.Main, so you can safely update the UI from within the callback.

Performance: Data collection typically completes in under 50ms, and the total time (including network request) is typically under 100ms. The SDK uses connection, read, and write timeouts of 30 seconds each.

kotlin
import kotlinx.coroutines.launch

fun submitFingerprint(userId: String) {
    val sdk = KeverdFingerprint.init(context, config)
    
    lifecycleScope.launch {
        sdk.submit(userId) { result ->
            when (result) {
                is Result.Success -> {
                    val riskScore = result.score
                    val requestId = result.requestId
                    
                    // Handle success
                    if (riskScore > 0.7) {
                        showSecurityAlert()
                    } else {
                        proceedWithTransaction()
                    }
                }
                
                is Result.Error -> {
                    Log.e("KeverdSDK", "Error: \${result.error}", result.throwable)
                    showError(result.error)
                }
                
                is Result.ConsentRequired -> {
                    requestUserConsent()
                }
            }
        }
    }
}

API Reference

KeverdFingerprint

Main SDK class for fingerprint collection and submission.

init(context: Context, config: Config): KeverdFingerprint

Initializes the SDK singleton instance. This method follows the singleton pattern, so subsequent calls with different configurations will return the same instance that was first created. The SDK uses the application context internally to prevent memory leaks.

Parameters:

  • context: Application or Activity context. The SDK will use context.applicationContext internally.
  • config: SDK configuration object (see Config section below).

Returns: Initialized KeverdFingerprint instance (singleton)

Thread Safety: Thread-safe singleton pattern using @Volatile and synchronized

Throws: IllegalArgumentException if apiBaseUrl does not start with "https://"

setConsent(granted: Boolean)

Sets user consent for data collection. This method is thread-safe and can be called from any thread. The consent state is stored in memory and persists for the lifetime of the SDK instance.

Parameters:

  • granted: true if user granted consent, false otherwise.

Important: Must be called before submit() if config.consentRequired == true. If consent is not granted, submit() will return Result.ConsentRequired.

Note: If consentRequired is false, consent is automatically granted during initialization.

submit(userId: String, onResult: (Result) -> Unit)

Submits fingerprint data and receives risk score via callback. This is a suspend function that must be called from a coroutine scope. The method collects device, SIM, session, and behavioral data, hashes sensitive identifiers, and sends the data to the Keverd API.

Parameters:

  • userId: User identifier (string). This is not hashed and is sent as-is to the API. If not provided or empty, the backend will auto-generate an identifier from the device fingerprint.
  • onResult: Callback function that receives a Result object. The callback is executed on the main thread.

Threading: Data collection and network calls run on Dispatchers.IO to avoid blocking the main thread. The callback executes on Dispatchers.Main, so you can safely update the UI.

Performance: Data collection typically completes in under 50ms. Total time (including network request) is typically under 100ms (p99 latency). Network timeouts are set to 30 seconds for connection, read, and write operations.

Error Handling: All exceptions are caught and returned as Result.Error. Network errors, JSON parsing errors, and other exceptions are included in the error message.

Config

Configuration data class for SDK initialization.

The Config data class is used to configure the SDK during initialization. All parameters are validated during object creation.

ParameterTypeRequiredDefaultDescription
apiBaseUrlStringYesBase URL for the fingerprint API endpoint. Must start with "https://" (HTTP is not allowed for security). The default production endpoint is https://api.keverd.com. The SDK will throw an IllegalArgumentException if the URL does not use HTTPS.
apiKeyStringYesAPI key for authenticating requests to the Keverd API. Obtain your API key from the API Keys page in the dashboard. The API key is sent in the request header as x-keverd-key. Keep your API key secure and never commit it to version control.
consentRequiredBooleanNotrueWhether explicit user consent is required before data collection. If true, you must call setConsent(true) before calling submit(). If false, consent is automatically granted during initialization. Set to false only if you have obtained consent through other means (e.g., terms of service acceptance).

Result Types

The SDK returns one of these result types:

The SDK uses a sealed class Result to represent the outcome of a fingerprint submission. This ensures type safety when handling results using Kotlin's when expressions.

Result.Success

Returned when the fingerprint submission is successful and a risk score is received from the API.

kotlin
data class Success(
    val score: Double,      // Risk score (0.0 to 1.0, where 1.0 is highest risk)
    val requestId: String   // Unique request identifier (UUID)
)

score: Risk score as a double between 0.0 (lowest risk) and 1.0 (highest risk). This is the normalized score. You can multiply by 100 to get a percentage (0-100). Use this score to make security decisions in your app.

requestId: Unique identifier (UUID) for this request. Use this to track and correlate events, debug issues, or reference the request in support tickets.

Result.Error

Returned when an error occurs during data collection or API submission. This can include network errors, API errors, JSON parsing errors, or other exceptions.

kotlin
data class Error(
    val error: String,              // Human-readable error message
    val throwable: Throwable? = null // Optional exception for debugging
)

error: Human-readable error message describing what went wrong. Examples: "Network error", "Invalid API key", "JSON parsing failed".

throwable: Optional exception object that caused the error. This is useful for debugging and logging. May be null if the error was not caused by an exception.

Common Errors:

  • Network connectivity issues (no internet, timeout)
  • Invalid or expired API key
  • Server errors (5xx status codes)
  • JSON parsing errors
  • Missing required permissions

Result.ConsentRequired

Returned when user consent is required but has not been granted. This only occurs if config.consentRequired == true and setConsent(true) has not been called.

kotlin
object ConsentRequired

Handling: When you receive this result, show a consent dialog to the user. Once consent is granted, call setConsent(true) and retry the submit() call.

Data Collection

The SDK collects four types of data: device information, SIM card data, session information, and behavioral data. All sensitive identifiers are SHA-256 hashed client-side before transmission to ensure privacy and compliance with data protection regulations.

Device Information

Collected by the DeviceCollector class. Includes:

  • Device ID: First 32 characters of the device fingerprint hash
  • Fingerprint: SHA-256 hash (64 hex characters) of device characteristics including manufacturer, model, OS version, screen dimensions, timezone, and locale
  • Manufacturer: Device manufacturer (e.g., "Samsung", "Google", "Xiaomi")
  • Model: Device model name (e.g., "SM-G991B", "Pixel 7")
  • OS Version: Android version (e.g., "13", "14")
  • Screen Dimensions: Screen width and height in pixels
  • Timezone: IANA timezone identifier (e.g., "America/New_York", "Africa/Nairobi")
  • Locale: Device locale (e.g., "en-US", "sw-KE")

Privacy: The fingerprint is computed client-side and only the hash is transmitted. No raw device identifiers are sent.

SIM Card Information

Collected by the SimCollector class. Requires READ_PHONE_STATE permission.

  • SIM Serial Number: SHA-256 hashed SIM card serial number (ICCID)
  • Phone Number: SHA-256 hashed phone number (if available)
  • Network Operator: Mobile network operator name (e.g., "Safaricom", "Airtel")
  • Network Type: Network type (e.g., "LTE", "5G", "3G")

Note: If READ_PHONE_STATE permission is not granted, SIM data collection is skipped gracefully. The SDK will still function, but SIM swap detection features will not be available.

Session Information

Collected by the SessionCollector class. Tracks session and installation data.

  • Session ID: Unique session identifier (UUID) generated for each app session
  • Timestamp: ISO 8601 formatted timestamp of the current session
  • Session Count: Number of sessions for this device (stored in SharedPreferences)
  • First Session: ISO 8601 formatted timestamp of the first session (when the app was first installed)

Storage: Session data is persisted in SharedPreferences with the key prefix keverd_sdk_.

Behavioral Data

Collected by the BehavioralCollector class. Tracks user interaction patterns for behavioral biometrics.

  • Typing Dwell Time: Array of milliseconds representing how long keys are held down (typically 5-20 samples)
  • Typing Flight Time: Array of milliseconds representing time between key releases (typically 5-20 samples)
  • Swipe Velocity: Average swipe velocity in pixels per millisecond (0.0 if no swipe data available)
  • Session Entropy: Shannon entropy value based on event diversity. Higher values indicate more diverse user interactions (0.0 if no events collected)

Collection: Behavioral data is collected passively as users interact with your app. The collector tracks touch events, scroll gestures, and keyboard input. Data collection starts automatically when the SDK is initialized.

Hashing and Privacy

All sensitive identifiers are hashed using SHA-256 before transmission. The HashUtil class handles all hashing operations.

  • Device Fingerprint: Computed from a combination of device characteristics and hashed to 64 hex characters
  • SIM Serial Number: Hashed using SHA-256
  • Phone Number: Hashed using SHA-256 (if available)
  • No Raw PII: No personally identifiable information is transmitted in plain text

Compliance: This approach ensures compliance with GDPR, CCPA, and other data protection regulations. The hashed identifiers cannot be reverse-engineered to reveal the original values.

Complete Example

Here's a complete example showing how to integrate the SDK into an Android Activity. This example demonstrates initialization, consent handling, fingerprint submission, and result processing.

kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.keverd.sdk.Config
import com.keverd.sdk.KeverdFingerprint
import com.keverd.sdk.Result
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    
    private lateinit var sdk: KeverdFingerprint
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Initialize SDK
        val config = Config(
            apiBaseUrl = "https://api.keverd.com",
            apiKey = "your-api-key",
            consentRequired = true
        )
        sdk = KeverdFingerprint.init(this, config)
        
        // Request consent
        requestConsent()
    }
    
    private fun requestConsent() {
        showConsentDialog(
            onAccept = {
                sdk.setConsent(true)
                submitFingerprint("user123")
            },
            onDecline = {
                sdk.setConsent(false)
                // Handle declined consent
            }
        )
    }
    
    private fun submitFingerprint(userId: String) {
        lifecycleScope.launch {
            sdk.submit(userId) { result ->
                when (result) {
                    is Result.Success -> {
                        handleRiskScore(result.score, result.requestId)
                    }
                    is Result.Error -> {
                        handleError(result.error)
                    }
                    is Result.ConsentRequired -> {
                        requestConsent()
                    }
                }
            }
        }
    }
    
    private fun handleRiskScore(score: Double, requestId: String) {
        when {
            score >= 0.8 -> {
                requireAdditionalVerification()
            }
            score >= 0.5 -> {
                logSuspiciousActivity(requestId)
            }
            else -> {
                proceedNormally()
            }
        }
    }
}

Error Handling

The SDK handles errors gracefully and returns them as Result.Error. Here's how to handle different error scenarios:

Network Errors

Network errors occur when the device cannot connect to the API or when the request times out. The SDK uses 30-second timeouts for connection, read, and write operations.

kotlin
is Result.Error -> {
    when {
        result.error.contains("timeout", ignoreCase = true) -> {
            // Handle timeout - retry with exponential backoff
            retryWithBackoff()
        }
        result.error.contains("network", ignoreCase = true) -> {
            // Handle network connectivity issues
            showNetworkError()
        }
        else -> {
            // Handle other network errors
            Log.e("KeverdSDK", "Network error: \${result.error}", result.throwable)
        }
    }
}

API Errors

API errors occur when the server returns an error response (4xx or 5xx status codes). Common API errors include invalid API key, rate limiting, and server errors.

kotlin
is Result.Error -> {
    when {
        result.error.contains("401", ignoreCase = true) || 
        result.error.contains("unauthorized", ignoreCase = true) -> {
            // Invalid or expired API key
            Log.e("KeverdSDK", "API key error: \${result.error}")
            // Prompt user to check API key configuration
        }
        result.error.contains("429", ignoreCase = true) || 
        result.error.contains("rate limit", ignoreCase = true) -> {
            // Rate limit exceeded
            Log.w("KeverdSDK", "Rate limit exceeded: \${result.error}")
            // Implement exponential backoff
        }
        result.error.contains("500", ignoreCase = true) -> {
            // Server error - retry after delay
            Log.e("KeverdSDK", "Server error: \${result.error}")
            retryAfterDelay()
        }
        else -> {
            Log.e("KeverdSDK", "API error: \${result.error}", result.throwable)
        }
    }
}

Data Collection Errors

Data collection errors can occur if required permissions are missing or if there's an issue accessing device information. The SDK handles these gracefully and continues with available data.

kotlin
is Result.Error -> {
    if (result.error.contains("permission", ignoreCase = true)) {
        // Missing required permission
        requestMissingPermission()
    } else {
        // Other data collection errors
        Log.e("KeverdSDK", "Collection error: \${result.error}", result.throwable)
        // SDK will still attempt to submit with available data
    }
}

Best Practices

  • Always check the error message and handle different error types appropriately
  • Log errors with the throwable for debugging purposes
  • Implement retry logic with exponential backoff for transient errors (network, 5xx)
  • Don't retry on client errors (4xx) unless the error message indicates it's safe to do so
  • Show user-friendly error messages for network and permission errors
  • Monitor error rates and patterns to identify issues early

Best Practices

Initialization

  • Initialize the SDK in your Application class's onCreate() method
  • Use the application context, not an activity context, to prevent memory leaks
  • Store your API key securely (e.g., in BuildConfig, environment variables, or a secure storage solution)
  • Never commit API keys to version control
  • Use different API keys for development, staging, and production environments

Consent Management

  • Always request consent before data collection if consentRequired is true
  • Show a clear, user-friendly consent dialog explaining what data is collected and why
  • Respect user's choice - if consent is declined, do not attempt to collect data
  • Store consent status if needed for future sessions (the SDK does not persist consent across app restarts)
  • Allow users to revoke consent at any time by calling setConsent(false)

Permission Handling

  • Request READ_PHONE_STATE permission at runtime on Android 6.0+
  • Explain why the permission is needed before requesting it
  • Handle permission denial gracefully - the SDK will still work without SIM data
  • Check permission status before calling submit() if SIM data is critical for your use case

Risk Score Usage

  • Use risk scores to make security decisions, not as the sole factor
  • Combine risk scores with other signals (e.g., login history, device trust, user behavior)
  • Implement graduated security measures based on risk levels (0-29: allow, 30-49: soft challenge, 50-69: hard challenge, 70-100: block)
  • Log risk scores and decisions for auditing and analysis
  • Monitor risk score distributions to identify anomalies or issues
  • Use the requestId to correlate events and debug issues

Performance

  • Call submit() from a coroutine scope (lifecycleScope or viewModelScope)
  • Don't block the main thread - the SDK handles threading automatically
  • Avoid calling submit() too frequently (e.g., on every screen transition)
  • Consider caching risk scores for a short period to avoid redundant API calls
  • Implement retry logic with exponential backoff for failed requests

Security

  • Always use HTTPS for API endpoints (enforced by the SDK)
  • Store API keys securely and never expose them in client-side code if possible
  • Use ProGuard or R8 to obfuscate your code and protect API keys
  • Monitor API key usage and rotate keys regularly
  • Implement certificate pinning if required by your security policy
  • Review and update dependencies regularly for security patches

Features

Privacy-First

All identifiers are SHA-256 hashed client-side before transmission

High Performance

Data collection < 50ms, total response time < 100ms

Lightweight

SDK size < 600 KB

Consent Management

Built-in consent checking before data collection

Requirements

The Android SDK has the following minimum requirements for integration:

  • Min SDK: 21 (Android 5.0 Lollipop). This ensures compatibility with 99%+ of active Android devices.
  • Target SDK: 36 (Android 14). Always target the latest SDK version for best security and performance.
  • Kotlin: 1.9 or higher. The SDK is written in Kotlin and uses modern Kotlin features.
  • Java: 11 or higher. Required for Kotlin compilation and runtime.
  • Internet Connection: Required for API communication. The SDK handles offline scenarios gracefully.
  • Permissions: INTERNET and ACCESS_NETWORK_STATE are required. READ_PHONE_STATE is optional but recommended for SIM data collection.

Dependencies

The SDK includes the following dependencies (automatically resolved when using Maven Central):

  • AndroidX Core KTX 1.10.1+
  • Kotlin Coroutines Core 1.7.3+
  • Kotlin Coroutines Android 1.7.3+
  • Retrofit 2.9.0+
  • OkHttp 4.12.0+
  • Gson 2.10.1+