Wink Identity Mobile SDK (Android)

Wink Android SDK

Identity + Wallet SDK for Android Apps

The Wink Android SDK allows mobile apps to integrate biometric identity onboarding, authentication flows, profile management, and wallet experiences. The SDK is delivered as a Flutter-powered AAR and integrates seamlessly into native Android apps.

SDK Version: 3.1.0


Table of Contents


Requirements

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

Installation

Step 1 — Add Maven Repositories

Add the following to settings.gradle:

dependencyResolutionManagement {
    repositories {
        maven { url = uri("https://storage.googleapis.com/download.flutter.io") }
        maven { url = uri("https://pkgs.dev.azure.com/payfave/wink-payfave-public/_packaging/wink-sdk/maven/v1") }

        // Regula document reader SDK
        maven { url = uri("https://maven.regulaforensics.com/RegulaDocumentReader") }

        // Jitpack — for com.github.canardoux:flutter_sound_core and other GitHub-hosted libs
        maven { url = uri("https://jitpack.io") }
        
        // site24x7's native APM lib lives on Zoho's own Maven server 
        maven { url = uri("https://maven.zohodl.com") }

        google()
        mavenCentral()
    }
}

Authentication is not required. The Maven feed is public.

Step 2 — Add SDK Dependency

Add to app/build.gradle:

dependencies {
    implementation("com.wink.wink_identity:flutter_release:1.0.10")
    implementation("com.wink:wink-identity-android:3.1.0")
}

Gradle will automatically download all required Flutter plugin dependencies.


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 scanning
RECORD_AUDIOVoice biometric enrollment
INTERNETAPI communication
ACCESS_NETWORK_STATENetwork connectivity checks
READ_EXTERNAL_STORAGEImage picker for document scanning

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.


SDK 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
    
    //For Google SignIn you need to create a googleClientId on google cloud console then you will get id and paste it here
    googleServerClientId="Your GoogleClientID"
))

⚠️ Must be called before launching SDK flows.


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).


Launching SDK Flows

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

// For 2FA authentication you have 2 options("voice" and "palm") select according to you and pass it in (requiredEnrollment = "")

WinkSDKAdapter.authenticate(activity = this,WinkEnrollRequest(requiredEnrollment = "palm")) { 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",
    showBackBtn = false  // optional
) { 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}")
}

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) }

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) }

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

Verify

var showVerify by remember { mutableStateOf(false) }

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

Payment

var showPayment by remember { mutableStateOf(false) }

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

Open Wallet

var showWallet by remember { mutableStateOf(false) }

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

Kotlin Coroutines

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",
    showBackBtn = false  // optional
)

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

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 = {
    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 and 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:).

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 = 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

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 Examples

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)

        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
        }

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

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

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

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 }
            )
        }
    }
}

Configuration Reference

FieldTypeDefaultDescription
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 ("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
googleServerClientIdStringrequiredyour GoogleClientId for googleSignIn

SDK Architecture

Host App
   │
   ▼
WinkSDKAdapter
   │
   ▼
PortraitFlutterActivity
   │
   ▼
Flutter Engine
   │
   ▼
WinkChannelPlugin
   │
   ▼
Wink Backend APIs

Integration Checklist

  • Maven repositories added (including Regula and JitPack)
  • SDK dependency added
  • Permissions auto-merged (no manual manifest changes needed)
  • Private key placed in res/raw/
  • signingKeyRawResourceId set in configuration
  • WinkSDKAdapter.configure() called at app startup
  • onTokenRefreshRequested callback implemented
  • Okta WinkAuthProvider assigned (if using Okta SSO)

Troubleshooting

IssueSolution
SDK not launchingEnsure configure() was called before launching flows
Crypto signing failsCheck signingKeyRawResourceId points to a valid PEM
wink_not_configured errorCall WinkSDKAdapter.configure() before any SDK method
wink_engine_failed_to_runVerify all Maven repos are present, including Flutter one
Slow first launchEnable autoPreWarm = true (default) or call preWarmEngine()
Okta flow not triggeringEnsure oktaClientId is set and authProvider is assigned
Compose flow not dismissingEnsure onDismiss sets isPresented back to false

Support

For merchant onboarding and API credentials contact the Wink Engineering Team.