Wink Identity Mobile SDK (Android)


Wink Android SDK Integration Guide

Requirements

RequirementMinimum
Android minSdk24 (Android 7.0)
Android compileSdk34
Kotlin2.1.0+
JVM Target11
Gradle7.0+

Installation

  1. Obtain the Wink SDK Maven repository (the aar_distribution/ directory or a hosted Maven URL).

  2. Add the Maven repository in your settings.gradle:

dependencyResolutionManagement {
    repositories {
        maven { url '/path/to/aar_distribution' }  // local path or hosted URL
        google()
        mavenCentral()
    }
}
  1. Add the dependencies in your app/build.gradle:
dependencies {
    releaseImplementation 'com.example.wink_identity:flutter_release:1.0'
    implementation 'com.wink:wink-identity-android:2.1.1'
}

Permissions

The SDK's Flutter plugins auto-merge the required permissions into your app's manifest. No manual declarations are needed:

PermissionUsage
CAMERAFace and palm biometric enrollment, ID document scanning
RECORD_AUDIOVoice biometric enrollment
INTERNETAPI communication
ACCESS_NETWORK_STATENetwork connectivity checks
READ_EXTERNAL_STORAGEImage picker for document scanning

Configuration

Configure the SDK once at app startup (e.g. in Application.onCreate() or your launch Activity.onCreate()).

Basic Configuration

import com.wink.identity.*

WinkSDKAdapter.configure(applicationContext, WinkConfiguration(
    keyId = "your_key_id",
    merchantId = "your_merchant_id",
    environment = "prod"  // "dev", "staging", or "prod"
))

Full Configuration

WinkSDKAdapter.configure(applicationContext, WinkConfiguration(
    keyId = "your_key_id",
    merchantId = "your_merchant_id",

    // Biometric / Face
    livenessEnabled = true,
    faceConfidenceThreshold = 90,
    mfaOrder = listOf("face", "voice"),
    retriesAllowed = 3,

    // Palm
    enablePalm = true,
    allowPalmFlip = false,
    palmCameraDir = 1,
    mfaGestureLiveness = false,
    enrollGestureLiveness = false,

    // OAuth / SSO
    auth0Domain = "your-tenant.auth0.com",
    auth0ClientId = "your_client_id",
    auth0MtmClientId = "your_mtm_client_id",
    auth0MtmClientSecret = "your_mtm_client_secret",
    oktaClientId = "your_okta_client_id",
    allowAppleLogin = true,
    allowGoogleLogin = true,
    allowPazeLogin = false,
    iamFirst = true,
    iamFirstProvider = "auth0",

    // Features
    allowIdScan = true,
    enableVoice = true,
    enableDocumentVerification = true,

    // Environment
    environment = "prod",

    // Signing key (from Android raw resource)
    signingKeyRawResourceId = R.raw.wink_private_key,

    // Engine pre-warming (default: true)
    autoPreWarm = true
))

Configuration Parameters

ParameterTypeDefaultDescription
keyIdStringrequiredYour Wink API key ID
merchantIdStringrequiredYour merchant identifier
environmentString"dev""dev", "staging", or "prod"
livenessEnabledBooleanfalseEnable face liveness detection
faceConfidenceThresholdInt90Face match confidence threshold (0-100)
mfaOrderList<String>[]MFA method priority order. Options: "Palm", "Voice", "Okta"
retriesAllowedInt0Number of biometric retries before failure
presentExtProfileBooleanfalseShow extended profile fields
enablePalmBooleanfalseEnable palm biometric
allowPalmFlipBooleanfalseAllow palm flip gesture
palmCameraDirInt1Palm camera direction
mfaGestureLivenessBooleanfalseEnable gesture-based liveness during MFA
enrollGestureLivenessBooleanfalseEnable gesture-based liveness during enrollment
auth0DomainString?nullAuth0 tenant domain
auth0ClientIdString?nullAuth0 client ID
auth0MtmClientIdString?nullAuth0 machine-to-machine client ID
auth0MtmClientSecretString?nullAuth0 machine-to-machine client secret
oktaClientIdString?nullOkta client ID for native Okta SSO
allowAppleLoginBooleanfalseEnable Sign in with Apple
allowGoogleLoginBooleanfalseEnable Google Sign-In
allowPazeLoginBooleanfalseEnable Paze login
iamFirstBooleanfalseShow IAM login before biometrics
iamFirstProviderString?nullIAM provider name ("auth0", "okta", "google", "apple"). Required when iamFirst is true
allowIdScanBooleanfalseEnable ID document scanning
enableVoiceBooleanfalseEnable voice biometric
enableDocumentVerificationBooleanfalseEnable document verification
returnAgeStatusBooleanfalseReturn age verification status in responses
signingKeyRawResourceIdInt?nullAndroid raw resource ID for signing PEM (e.g. R.raw.wink_private_key)
autoPreWarmBooleantruePre-warm Flutter engine on configure

Okta SSO Setup

If your integration uses Okta SSO (enabled by setting oktaClientId in configuration), complete these steps.

1. Implement WinkAuthProvider

The SDK calls your provider mid-flow when it needs Okta authentication or token revocation:

interface WinkAuthProvider {
    fun performBrowserSignIn(completion: (WinkAuthResult) -> Unit)
    fun revokeCurrentToken(completion: (Boolean) -> Unit)
}

Implement it and assign before launching SDK flows:

class MyOktaProvider : WinkAuthProvider {
    override fun performBrowserSignIn(completion: (WinkAuthResult) -> Unit) {
        // Launch Okta sign-in (e.g. via Okta OIDC SDK), then:
        completion(WinkAuthResult(
            idToken = idToken,
            givenName = user.givenName,
            familyName = user.familyName,
            email = user.email,
            userId = user.sub
        ))
    }

    override fun revokeCurrentToken(completion: (Boolean) -> Unit) {
        // Revoke stored token. Best-effort.
        completion(true)
    }
}

// After configure():
WinkSDKAdapter.authProvider = MyOktaProvider()

2. Configure Okta Parameters

WinkSDKAdapter.configure(applicationContext, WinkConfiguration(
    keyId = "your_key_id",
    merchantId = "your_merchant_id",
    oktaClientId = "your_okta_client_id",     // enables native Okta
    mfaOrder = listOf("Palm", "Voice", "Okta"), // include Okta in MFA chain
    iamFirst = true,                           // optional: show Okta login before biometrics
    iamFirstProvider = "okta",                 // required when iamFirst is true
    environment = "prod",
    signingKeyRawResourceId = R.raw.wink_private_key
))

When oktaClientId is set, the SDK uses native Okta browser sign-in. When it is null, the SDK falls back to Auth0-hosted Okta authentication (requires auth0Domain and auth0ClientId).


Signing Key Setup

The SDK signs API requests using an RSA private key. The key is held in memory (not persisted to Android Keystore). There are two ways to provide the key:

Option A: Auto-import from raw resource (recommended)

Place your wink_private_key.pem file in res/raw/, then set signingKeyRawResourceId in your configuration:

WinkSDKAdapter.configure(applicationContext, WinkConfiguration(
    keyId = "your_key_id",
    merchantId = "your_merchant_id",
    signingKeyRawResourceId = R.raw.wink_private_key  // res/raw/wink_private_key.pem
))

Option B: Manual import

// Import from a PEM string directly
WinkKeystoreSigner.shared.importPrivateKey(pemString)

// Or import from a raw resource
WinkKeystoreSigner.shared.importPrivateKeyFromResource(context, R.raw.wink_private_key)

Signing Key Utilities

// Check if a signing key is present
if (WinkKeystoreSigner.shared.hasSigningKey) {
    println("Signing key is ready")
}

// Remove the signing key (e.g. on logout)
WinkKeystoreSigner.shared.clearSigningKey()

Supported formats: Both PKCS#8 and PKCS#1 PEM keys are accepted. PKCS#1 keys are automatically wrapped to PKCS#8 internally.


Usage — Activity / Fragment

All SDK methods launch a portrait-locked Flutter activity from the activity you provide. Results are delivered via a callback on the main thread.

Enroll a New User

val request = WinkEnrollRequest(
    requiredEnrollment = "face",   // "face", "voice", or "palm"
    firstName = "Jane",
    lastName = "Doe",
    email = "[email protected]"
)

WinkSDKAdapter.enroll(activity = this, request = request) { result ->
    if (result.isSuccess) {
        println("Enrolled! Token: ${result.winkToken}")
    } else {
        println("Enrollment failed: ${result.message}")
    }
}

Authenticate an Existing User

WinkSDKAdapter.authenticate(activity = this) { result ->
    if (result.isSuccess) {
        println("Authenticated: ${result.winkToken}")
        println("Access Token: ${result.accessToken}")
    }
}

Verify Identity

WinkSDKAdapter.verify(
    activity = this,
    winkTokenComp = "existing_wink_token",
    documentCheck = true
) { result ->
    if (result.isSuccess) {
        println("Verification passed")
    }
}

Show Profile / Account Management

WinkSDKAdapter.profile(
    activity = this,
    accessToken = "user_access_token",
    displayMode = "combined"  // "profile", "wallet", or "combined"
) { result ->
    println("Profile closed")
}

Launch (Typed Convenience)

A single entry point for post-authentication flows with a typed WinkLaunchMode:

WinkSDKAdapter.launch(
    activity = this,
    mode = WinkLaunchMode.COMBINED,  // ACCOUNT, WALLET, or COMBINED
    accessToken = "user_access_token"
) { result ->
    println("Launch complete")
}

Payment / Wallet

val walletConfig = WinkWalletConfig(
    accessToken = "user_access_token",
    merchantId = "merchant_xyz",
    extra = mapOf("customField" to "value")  // additional fields forwarded to Flutter
)

WinkSDKAdapter.start(activity = this, config = walletConfig) { result ->
    if (result.isSuccess) {
        println("Payment status: ${result.status}")
        println("Transaction ID: ${result.transactionId}")
    }
}

Open Wallet Directly

WinkSDKAdapter.openWallet(
    activity = this,
    accessToken = "user_access_token",
    merchantId = "merchant_xyz"
) { result ->
    println("Wallet closed: ${result.status}")
}

Usage — Kotlin Coroutines (async/await)

All methods have suspend overloads:

// Authenticate
val result = WinkSDKAdapter.authenticateAsync(activity = this)

// Enroll
val enrollResult = WinkSDKAdapter.enrollAsync(
    activity = this,
    request = WinkEnrollRequest(requiredEnrollment = "face")
)

// Profile
val profileResult = WinkSDKAdapter.profileAsync(
    activity = this,
    accessToken = "token"
)

// Launch
val launchResult = WinkSDKAdapter.launchAsync(
    activity = this,
    mode = WinkLaunchMode.COMBINED,
    accessToken = "token"
)

// Open Wallet
val walletResult = WinkSDKAdapter.openWalletAsync(
    activity = this,
    accessToken = "token"
)

Usage — Jetpack Compose

The SDK provides Composable wrappers for all flows. Control presentation with a Boolean state:

Authenticate

import com.wink.identity.compose.*

@Composable
fun LoginScreen() {
    var showAuth by remember { mutableStateOf(false) }
    var token by remember { mutableStateOf<String?>(null) }

    Button(onClick = { showAuth = true }) {
        Text("Sign In")
    }

    WinkAuthenticate(
        isPresented = showAuth,
        onResult = { result -> token = result.winkToken },
        onDismiss = { showAuth = false }
    )
}

Enroll

var showEnroll by remember { mutableStateOf(false) }

Button(onClick = { showEnroll = true }) {
    Text("Enroll")
}

WinkEnroll(
    isPresented = showEnroll,
    request = WinkEnrollRequest(requiredEnrollment = "face"),
    onResult = { result -> println(result.winkToken ?: "cancelled") },
    onDismiss = { showEnroll = false }
)

Launch (Account / Wallet / Combined)

var showProfile by remember { mutableStateOf(false) }

Button(onClick = { showProfile = true }) {
    Text("My Account")
}

WinkLaunch(
    isPresented = showProfile,
    mode = WinkLaunchMode.COMBINED,
    accessToken = accessToken,
    onResult = { result -> /* handle result */ },
    onDismiss = { showProfile = false }
)

Verify

var showVerify by remember { mutableStateOf(false) }

Button(onClick = { showVerify = true }) {
    Text("Verify Identity")
}

WinkVerify(
    isPresented = showVerify,
    winkTokenComp = winkToken,
    documentCheck = true,
    onResult = { result -> /* handle result */ },
    onDismiss = { showVerify = false }
)

Payment

var showPayment by remember { mutableStateOf(false) }

Button(onClick = { showPayment = true }) {
    Text("Pay")
}

WinkPayment(
    isPresented = showPayment,
    config = WinkWalletConfig(accessToken = accessToken),
    onResult = { result -> println(result.status ?: "") },
    onDismiss = { showPayment = false }
)

Open Wallet

var showWallet by remember { mutableStateOf(false) }

Button(onClick = { showWallet = true }) {
    Text("Wallet")
}

WinkWallet(
    isPresented = showWallet,
    accessToken = accessToken,
    merchantId = "merchant_xyz",
    onResult = { result -> /* handle result */ },
    onDismiss = { showWallet = false }
)

Token Management

Push updated tokens to all active SDK sessions. If no session is live, the update is queued and delivered when the next session starts.

WinkSDKAdapter.updateTokens(
    accessToken = newAccessToken,
    refreshToken = newRefreshToken
)

Token Refresh Callback

The SDK notifies your app when it needs a fresh token:

WinkSDKAdapter.onTokenRefreshRequested = {
    // Refresh your token (e.g. via your token manager)
    val newToken = TokenManager.refreshAccessToken()
    WinkSDKAdapter.updateTokens(accessToken = newToken, refreshToken = newRefresh)
}

Engine Pre-Warming

The Flutter engine is pre-warmed automatically when autoPreWarm is true (the default). This makes the first SDK launch near-instant.

If you set autoPreWarm = false, you can control timing manually:

// Pre-warm ahead of time (e.g. on the screen with a "Sign In" button)
WinkSDKAdapter.preWarmEngine(applicationContext)

// Tear down on memory pressure
override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
        WinkSDKAdapter.tearDownPreWarmedEngine()
    }
}

The pre-warmed engine is consumed on the next SDK launch. The engine is automatically re-warmed after each flow completes.


Result Types

WinkLoginResult

Returned by enroll, authenticate, profile, verify, and launch.

PropertyTypeDescription
winkTokenString?The authenticated user's Wink token
firstNameString?User's first name
lastNameString?User's last name
contactNoString?User's phone number
emailString?User's email
accessTokenString?OAuth access token
refreshTokenString?OAuth refresh token
externalIAMString?External IAM provider identifier
dateOfBirthString?User's date of birth
isProfileVerifiedBoolean?Whether the user's profile has been verified
oktaIdTokenString?Okta ID token (when using Okta SSO)
messageString?Error message (prefixed with wink_ on SDK errors)
isSuccessBooleantrue if no wink_* error occurred

WinkPaymentResult

Returned by start and openWallet.

PropertyTypeDescription
statusString?Payment status
transactionIdString?Transaction identifier
messageString?Error message
rawMap<String, Any>Full raw payload
isSuccessBooleantrue if no wink_* error occurred

WinkAuthResult

Passed to WinkAuthProvider.performBrowserSignIn(completion:). The host app constructs this from its own Okta sign-in result.

PropertyTypeDescription
idTokenString?Okta ID token
givenNameString?User's given name
familyNameString?User's family name
emailString?User's email
userIdString?Okta user ID (sub claim)
errorString?Error message (non-nil signals failure)

Error Handling

Configuration Errors

WinkConfiguration.ValidationError is thrown by configure():

ErrorDescription
MissingKeyIdkeyId is empty
MissingMerchantIdmerchantId is empty
EmptyEnvironmentenvironment is empty
InvalidIamFirstProvideriamFirstProvider must be set when iamFirst is true and null when false

Result-Level Errors

When a flow fails, WinkLoginResult.message or WinkPaymentResult.message will contain a wink_* prefixed string:

MessageMeaning
wink_not_configuredSDK was not configured
wink_engine_failed_to_runFlutter engine failed to start
wink_cancelledUser dismissed the flow without completing

Check result.isSuccess to quickly determine if the flow completed successfully.


ProGuard / R8

The SDK ships with consumer ProGuard rules that are applied automatically. No additional rules are needed in your app:

-keep class com.wink.identity.** { *; }
-keepclassmembers class com.wink.identity.** { *; }

Complete Activity Example

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.wink.identity.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 1. Configure the SDK
        WinkSDKAdapter.configure(applicationContext, WinkConfiguration(
            keyId = "your_key_id",
            merchantId = "your_merchant_id",
            signingKeyRawResourceId = R.raw.wink_private_key,
            environment = "prod"
        ))

        // 2. Set up token refresh callback
        WinkSDKAdapter.onTokenRefreshRequested = {
            // Your token refresh logic here
        }

        findViewById<Button>(R.id.signInButton).setOnClickListener {
            signIn()
        }
    }

    private fun signIn() {
        // 3. Authenticate
        WinkSDKAdapter.authenticate(activity = this) { result ->
            if (!result.isSuccess) {
                println("Auth failed: ${result.message}")
                return@authenticate
            }
            println("Welcome, ${result.firstName}!")
            println("Token: ${result.winkToken}")

            // 4. Open wallet after login
            val accessToken = result.accessToken ?: return@authenticate
            WinkSDKAdapter.openWallet(
                activity = this,
                accessToken = accessToken
            ) { paymentResult ->
                println("Wallet closed: ${paymentResult.status}")
            }
        }
    }
}

Complete Jetpack Compose Example

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import com.wink.identity.*
import com.wink.identity.compose.*

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

        WinkSDKAdapter.configure(applicationContext, WinkConfiguration(
            keyId = "your_key_id",
            merchantId = "your_merchant_id",
            signingKeyRawResourceId = R.raw.wink_private_key,
            environment = "prod"
        ))

        WinkSDKAdapter.onTokenRefreshRequested = {
            // Your token refresh logic here
        }

        setContent {
            MaterialTheme {
                MainScreen()
            }
        }
    }
}

@Composable
fun MainScreen() {
    var showAuth by remember { mutableStateOf(false) }
    var showWallet by remember { mutableStateOf(false) }
    var accessToken by remember { mutableStateOf<String?>(null) }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = { showAuth = true }) {
            Text("Sign In")
        }

        WinkAuthenticate(
            isPresented = showAuth,
            onResult = { result ->
                if (result.isSuccess) {
                    accessToken = result.accessToken
                }
            },
            onDismiss = { showAuth = false }
        )

        accessToken?.let { token ->
            Spacer(modifier = Modifier.height(16.dp))

            Button(onClick = { showWallet = true }) {
                Text("Open Wallet")
            }

            WinkWallet(
                isPresented = showWallet,
                accessToken = token,
                onResult = { result ->
                    println("Wallet: ${result.status}")
                },
                onDismiss = { showWallet = false }
            )
        }
    }
}