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
- Installation
- Permissions
- Signing Key Setup
- SDK Configuration
- Okta SSO Setup
- Launching SDK Flows
- Jetpack Compose
- Kotlin Coroutines
- Token Management
- Engine Pre-Warming
- Result Types
- Error Handling
- ProGuard / R8
- Complete Examples
- Configuration Reference
- SDK Architecture
- Integration Checklist
- Troubleshooting
- Support
Requirements
| Requirement | Minimum Version |
|---|---|
| Android minSdk | 24 (Android 7.0) |
| Android compileSdk | 36 |
| Kotlin | 2.1.0+ |
| JVM Target | 11 |
| Gradle | 7.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:
| Permission | Usage |
|---|---|
| CAMERA | Face and palm biometric enrollment, ID scanning |
| RECORD_AUDIO | Voice biometric enrollment |
| INTERNET | API communication |
| ACCESS_NETWORK_STATE | Network connectivity checks |
| READ_EXTERNAL_STORAGE | Image 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.
| Property | Type | Description |
|---|---|---|
| winkToken | String? | The authenticated user's Wink token |
| firstName | String? | User's first name |
| lastName | String? | User's last name |
| contactNo | String? | User's phone number |
| String? | User's email | |
| accessToken | String? | OAuth access token |
| refreshToken | String? | OAuth refresh token |
| externalIAM | String? | External IAM provider identifier |
| dateOfBirth | String? | User's date of birth |
| isProfileVerified | Boolean? | Whether the user's profile has been verified |
| oktaIdToken | String? | Okta ID token (when using Okta SSO) |
| message | String? | Error message (prefixed with wink_ on SDK errors) |
| isSuccess | Boolean | true if no wink_* error occurred |
WinkPaymentResult
Returned by start and openWallet.
| Property | Type | Description |
|---|---|---|
| status | String? | Payment status |
| transactionId | String? | Transaction identifier |
| message | String? | Error message |
| raw | Map<String, Any> | Full raw payload |
| isSuccess | Boolean | true if no wink_* error occurred |
WinkAuthResult
Passed to WinkAuthProvider.performBrowserSignIn(completion:).
| Property | Type | Description |
|---|---|---|
| idToken | String? | Okta ID token |
| givenName | String? | User's given name |
| familyName | String? | User's family name |
| String? | User's email | |
| userId | String? | Okta user ID (sub claim) |
| error | String? | Error message (non-nil = failure) |
Error Handling
Configuration Errors
WinkConfiguration.ValidationError is thrown by configure():
| Error | Description |
|---|---|
| MissingKeyId | keyId is empty |
| MissingMerchantId | merchantId is empty |
| EmptyEnvironment | environment is empty |
| InvalidIamFirstProvider | iamFirstProvider must be set when iamFirst is true and null when false |
Result-Level Errors
| Message | Meaning |
|---|---|
| wink_not_configured | SDK was not configured |
| wink_engine_failed_to_run | Flutter engine failed to start |
| wink_cancelled | User 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
| Field | Type | Default | Description |
|---|---|---|---|
| keyId | String | required | Your Wink API key ID |
| merchantId | String | required | Your merchant identifier |
| environment | String | "dev" | "dev", "staging", or "prod" |
| livenessEnabled | Boolean | false | Enable face liveness detection |
| faceConfidenceThreshold | Int | 90 | Face match confidence threshold (0–100) |
| mfaOrder | List<String> | [] | MFA method priority order. Options: "Palm", "Voice", "Okta" |
| retriesAllowed | Int | 0 | Number of biometric retries before failure |
| presentExtProfile | Boolean | false | Show extended profile fields |
| enablePalm | Boolean | false | Enable palm biometric |
| allowPalmFlip | Boolean | false | Allow palm flip gesture |
| palmCameraDir | Int | 1 | Palm camera direction |
| mfaGestureLiveness | Boolean | false | Enable gesture-based liveness during MFA |
| enrollGestureLiveness | Boolean | false | Enable gesture-based liveness during enrollment |
| auth0Domain | String? | null | Auth0 tenant domain |
| auth0ClientId | String? | null | Auth0 client ID |
| auth0MtmClientId | String? | null | Auth0 machine-to-machine client ID |
| auth0MtmClientSecret | String? | null | Auth0 machine-to-machine client secret |
| oktaClientId | String? | null | Okta client ID for native Okta SSO |
| allowAppleLogin | Boolean | false | Enable Sign in with Apple |
| allowGoogleLogin | Boolean | false | Enable Google Sign-In |
| allowPazeLogin | Boolean | false | Enable Paze login |
| iamFirst | Boolean | false | Show IAM login before biometrics |
| iamFirstProvider | String? | null | IAM provider ("auth0", "okta", "google", "apple"). Required when iamFirst is true |
| allowIdScan | Boolean | false | Enable ID document scanning |
| enableVoice | Boolean | false | Enable voice biometric |
| enableDocumentVerification | Boolean | false | Enable document verification |
| returnAgeStatus | Boolean | false | Return age verification status in responses |
| signingKeyRawResourceId | Int? | null | Android raw resource ID for signing PEM (e.g. R.raw.wink_private_key) |
| autoPreWarm | Boolean | true | Pre-warm Flutter engine on configure |
| googleServerClientId | String | required | your 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/ -
signingKeyRawResourceIdset in configuration -
WinkSDKAdapter.configure()called at app startup -
onTokenRefreshRequestedcallback implemented - Okta
WinkAuthProviderassigned (if using Okta SSO)
Troubleshooting
| Issue | Solution |
|---|---|
| SDK not launching | Ensure configure() was called before launching flows |
| Crypto signing fails | Check signingKeyRawResourceId points to a valid PEM |
| wink_not_configured error | Call WinkSDKAdapter.configure() before any SDK method |
| wink_engine_failed_to_run | Verify all Maven repos are present, including Flutter one |
| Slow first launch | Enable autoPreWarm = true (default) or call preWarmEngine() |
| Okta flow not triggering | Ensure oktaClientId is set and authProvider is assigned |
| Compose flow not dismissing | Ensure onDismiss sets isPresented back to false |
Support
For merchant onboarding and API credentials contact the Wink Engineering Team.
