Wink Identity Mobile SDK
Wink iOS SDK Integration Guide
Requirements
| Requirement | Minimum |
|---|---|
| iOS Deployment Target | 14.0 |
| Swift | 5.9+ |
| Xcode | 15.0+ |
Installation
Swift Package Manager (SPM)
- In Xcode, go to File > Add Package Dependencies...
- Enter the Wink SDK SPM distribution repository URL.
- Select the version rule and add the
WinkFlutterSDKlibrary to your target.
The SPM package bundles all required xcframeworks (Flutter engine, plugins, ML Kit, Regula, Plaid, VGS, etc.) automatically.
CocoaPods
Add the following to your Podfile:
platform :ios, '14.0'
target 'YourApp' do
use_frameworks!
pod 'wink_flutter_sdk', :path => '<path-to-sdk>'
endThen run:
pod installInfo.plist Permissions
The SDK uses the camera, microphone, and photo library. Add these keys to your Info.plist:
<key>NSCameraUsageDescription</key>
<string>Used for biometric face and palm enrollment</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used for voice biometric enrollment</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Used for document verification</string>
<key>NSFaceIDUsageDescription</key>
<string>Used for biometric authentication</string>Configuration
Configure the SDK once at app startup (e.g. in AppDelegate.application(_:didFinishLaunchingWithOptions:) or your SwiftUI App.init).
Basic Configuration
import WinkFlutterSDK
do {
try WinkSDKAdapter.shared.configure(WinkConfiguration(
keyId: "your_key_id",
merchantId: "your_merchant_id",
environment: "prod" // "dev", "staging", or "prod"
))
} catch {
print("SDK configuration failed: \(error)")
}Full Configuration
try WinkSDKAdapter.shared.configure(WinkConfiguration(
keyId: "your_key_id",
merchantId: "your_merchant_id",
// Biometric / Face
livenessEnabled: true,
faceConfidenceThreshold: 90,
mfaOrder: ["face", "voice"],
retriesAllowed: 3,
// Palm
enablePalm: true,
allowPalmFlip: false,
palmCameraDir: 1,
// OAuth / SSO
auth0Domain: "your-tenant.auth0.com",
auth0ClientId: "your_client_id",
allowAppleLogin: true,
allowGoogleLogin: true,
allowPazeLogin: false,
iamFirst: true,
iamFirstProvider: "auth0",
// Features
allowIdScan: true,
enableVoice: true,
enableDocumentVerification: true,
// Environment
environment: "prod",
// Signing key (auto-imported from bundle)
signingKeyResource: "wink_private_key", // name of .pem file in your bundle
signingKeyBundle: .main,
// Engine pre-warming (default: true)
autoPreWarm: true
))Configuration Parameters
| Parameter | 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 | Bool | false | Enable face liveness detection |
faceConfidenceThreshold | Int | 90 | Face match confidence threshold (0-100) |
mfaOrder | [String] | [] | MFA method priority order |
retriesAllowed | Int | 0 | Number of biometric retries before failure |
presentExtProfile | Bool | false | Show extended profile fields |
enablePalm | Bool | false | Enable palm biometric |
allowPalmFlip | Bool | false | Allow palm flip gesture |
palmCameraDir | Int | 1 | Palm camera direction |
auth0Domain | String? | nil | Auth0 tenant domain |
auth0ClientId | String? | nil | Auth0 client ID |
allowAppleLogin | Bool | false | Enable Sign in with Apple |
allowGoogleLogin | Bool | false | Enable Google Sign-In |
allowPazeLogin | Bool | false | Enable Paze login |
iamFirst | Bool | false | Show IAM login before biometrics |
iamFirstProvider | String? | nil | Required when iamFirst is true |
allowIdScan | Bool | false | Enable ID document scanning |
enableVoice | Bool | false | Enable voice biometric |
enableDocumentVerification | Bool | false | Enable document verification |
signingKeyResource | String? | nil | Bundle resource name for signing PEM (without .pem extension) |
signingKeyBundle | Bundle | .main | Bundle containing the signing key |
autoPreWarm | Bool | true | Pre-warm Flutter engine on configure |
Signing Key Setup
The SDK signs API requests using an RSA private key stored in the iOS Keychain. There are two ways to provide the key:
Option A: Auto-import from bundle (recommended)
Add your wink_private_key.pem file to your Xcode project, then set signingKeyResource in your configuration:
try WinkSDKAdapter.shared.configure(WinkConfiguration(
keyId: "your_key_id",
merchantId: "your_merchant_id",
signingKeyResource: "wink_private_key" // looks for wink_private_key.pem in the bundle
))Option B: Manual import
// Import from a PEM file in a specific bundle
try WinkSigning.shared.importPrivateKeyFromBundle(
resource: "wink_private_key",
ofType: "pem",
in: .main
)
// Or import from a PEM string directly
try WinkSigning.shared.importPrivateKey(fromPEM: pemString)Signing Key Utilities
// Check if a signing key is present
if WinkSigning.shared.hasSigningKey {
print("Signing key is ready")
}
// Remove the signing key (e.g. on logout)
WinkSigning.shared.clearSigningKey()Usage — UIKit
All SDK methods must be called from the main thread. The SDK presents its UI modally from the view controller you provide.
Enroll a New User
let request = WinkEnrollRequest(
requiredEnrollment: "face", // "face", "voice", or "palm"
firstName: "Jane",
lastName: "Doe",
email: "[email protected]"
)
WinkSDKAdapter.shared.enroll(request: request, from: self) { result in
if result.isSuccess {
print("Enrolled! Token: \(result.winkToken ?? "")")
} else {
print("Enrollment failed: \(result.message ?? "unknown")")
}
}Authenticate an Existing User
WinkSDKAdapter.shared.authenticate(from: self) { result in
if result.isSuccess {
print("Authenticated: \(result.winkToken ?? "")")
print("Access Token: \(result.accessToken ?? "")")
}
}Verify Identity
WinkSDKAdapter.shared.verify(
from: self,
winkTokenComp: "existing_wink_token",
documentCheck: true
) { result in
if result.isSuccess {
print("Verification passed")
}
}Show Profile / Account Management
WinkSDKAdapter.shared.profile(
from: self,
accessToken: "user_access_token",
displayMode: "combined" // "profile", "wallet", or "combined"
) { result in
print("Profile closed")
}Launch (Typed Convenience)
A single entry point for post-authentication flows with a typed WinkLaunchMode:
WinkSDKAdapter.shared.launch(
from: self,
mode: .combined, // .account, .wallet, or .combined
accessToken: "user_access_token"
) { result in
print("Launch complete")
}Payment / Wallet
let walletConfig = WinkWalletConfig(
accessToken: "user_access_token",
merchantId: "merchant_xyz",
extra: ["customField": "value"] // additional fields forwarded to Flutter
)
WinkSDKAdapter.shared.start(from: self, config: walletConfig) { result in
if result.isSuccess {
print("Payment status: \(result.status ?? "")")
print("Transaction ID: \(result.transactionId ?? "")")
}
}Open Wallet Directly
WinkSDKAdapter.shared.openWallet(
from: self,
accessToken: "user_access_token",
merchantId: "merchant_xyz"
) { result in
print("Wallet closed: \(result.status ?? "")")
}Usage — Swift Concurrency (async/await)
All methods have async overloads available on iOS 13+:
// Authenticate
let result = await WinkSDKAdapter.shared.authenticate(from: self)
// Enroll
let enrollResult = await WinkSDKAdapter.shared.enroll(
request: WinkEnrollRequest(requiredEnrollment: "face"),
from: self
)
// Verify
let verifyResult = await WinkSDKAdapter.shared.verify(
from: self,
winkTokenComp: "token",
documentCheck: true
)
// Profile
let profileResult = await WinkSDKAdapter.shared.profile(
from: self,
accessToken: "token"
)
// Launch
let launchResult = await WinkSDKAdapter.shared.launch(
from: self,
mode: .combined,
accessToken: "token"
)
// Payment
let paymentResult = await WinkSDKAdapter.shared.start(
from: self,
config: WinkWalletConfig(accessToken: "token")
)
// Open Wallet
let walletResult = await WinkSDKAdapter.shared.openWallet(
from: self,
accessToken: "token"
)Usage — SwiftUI
The SDK provides SwiftUI view modifiers for all flows (iOS 14+). Bind a Bool to control presentation:
Authenticate
struct LoginView: View {
@State private var showAuth = false
@State private var token: String?
var body: some View {
Button("Sign In") { showAuth = true }
.winkAuthenticate(isPresented: $showAuth) { result in
token = result.winkToken
}
}
}Enroll
Button("Enroll") { showEnroll = true }
.winkEnroll(
isPresented: $showEnroll,
request: WinkEnrollRequest(requiredEnrollment: "face")
) { result in
print(result.winkToken ?? "cancelled")
}Launch (Account / Wallet / Combined)
Button("My Account") { showProfile = true }
.winkLaunch(
isPresented: $showProfile,
mode: .combined,
accessToken: accessToken
) { result in
// handle result
}Verify
Button("Verify Identity") { showVerify = true }
.winkVerify(
isPresented: $showVerify,
winkTokenComp: winkToken,
documentCheck: true
) { result in
// handle result
}Payment
Button("Pay") { showPayment = true }
.winkPayment(
isPresented: $showPayment,
config: WinkWalletConfig(accessToken: accessToken)
) { result in
print(result.status ?? "")
}Open Wallet
Button("Wallet") { showWallet = true }
.winkWallet(
isPresented: $showWallet,
accessToken: accessToken,
merchantId: "merchant_xyz"
) { result in
// handle result
}Embedded View Controllers
For cases where you need to embed the SDK UI within your own view hierarchy instead of presenting modally:
Embedded Profile
do {
let vc = try WinkSDKAdapter.shared.makeProfileViewController(
accessToken: "user_access_token",
displayMode: "combined"
) { result in
print("Profile result: \(result.isSuccess)")
}
addChild(vc)
view.addSubview(vc.view)
vc.view.frame = containerView.bounds
vc.didMove(toParent: self)
} catch WinkSDKError.notConfigured {
print("SDK not configured — call configure() first")
} catch {
print("Engine failed: \(error)")
}Embedded Wallet
do {
let vc = try WinkSDKAdapter.shared.makeWalletViewController(
config: WinkWalletConfig(accessToken: "token")
) { result in
print("Wallet result: \(result.status ?? "")")
}
addChild(vc)
view.addSubview(vc.view)
vc.view.frame = containerView.bounds
vc.didMove(toParent: self)
} catch {
print("Failed: \(error)")
}Note: The returned view controller must be given a frame and added to your view hierarchy before its content renders. Call
setNeedsLayout()/layoutIfNeeded()after setting constraints.
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.shared.updateTokens(
accessToken: newAccessToken,
refreshToken: newRefreshToken
)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. in viewDidLoad of the screen with a "Sign In" button)
WinkSDKAdapter.shared.preWarmEngine()
// Tear down on memory pressure
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
WinkSDKAdapter.shared.tearDownPreWarmedEngine()
}The pre-warmed engine is consumed on the next SDK launch. Call preWarmEngine() again if you need a fast launch a second time.
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 |
email | String? | User's email |
accessToken | String? | OAuth access token |
refreshToken | String? | OAuth refresh token |
externalIAM | String? | External IAM provider identifier |
message | String? | Error message (prefixed with wink_ on SDK errors) |
isSuccess | Bool | 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 | [String: Any] | Full raw payload |
isSuccess | Bool | true if no wink_* error occurred |
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 nil when false |
Runtime Errors
WinkSDKError is thrown by embedded VC factory methods:
| Error | Description |
|---|---|
.notConfigured | configure() was not called before using the SDK |
.engineFailedToRun(underlying:) | The Flutter engine failed to start |
Result-Level Errors
When a flow fails, WinkLoginResult.message or WinkPaymentResult.message will contain a wink_* prefixed string:
| 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.
Complete UIKit Example
import UIKit
import WinkFlutterSDK
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 1. Configure the SDK
do {
try WinkSDKAdapter.shared.configure(WinkConfiguration(
keyId: "your_key_id",
merchantId: "your_merchant_id",
signingKeyResource: "wink_private_key",
environment: "prod"
))
} catch {
print("Configuration failed: \(error)")
}
}
@IBAction func signInTapped(_ sender: Any) {
// 2. Authenticate
WinkSDKAdapter.shared.authenticate(from: self) { result in
guard result.isSuccess else {
print("Auth failed: \(result.message ?? "unknown")")
return
}
print("Welcome, \(result.firstName ?? "")!")
print("Token: \(result.winkToken ?? "")")
// 3. Open wallet after login
guard let accessToken = result.accessToken else { return }
WinkSDKAdapter.shared.openWallet(
from: self,
accessToken: accessToken
) { paymentResult in
print("Wallet closed: \(paymentResult.status ?? "")")
}
}
}
}Complete SwiftUI Example
import SwiftUI
import WinkFlutterSDK
@main
struct MyApp: App {
init() {
do {
try WinkSDKAdapter.shared.configure(WinkConfiguration(
keyId: "your_key_id",
merchantId: "your_merchant_id",
signingKeyResource: "wink_private_key",
environment: "prod"
))
} catch {
print("Configuration failed: \(error)")
}
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
struct ContentView: View {
@State private var showAuth = false
@State private var showWallet = false
@State private var accessToken: String?
var body: some View {
VStack(spacing: 20) {
Button("Sign In") { showAuth = true }
.winkAuthenticate(isPresented: $showAuth) { result in
if result.isSuccess {
accessToken = result.accessToken
}
}
if let token = accessToken {
Button("Open Wallet") { showWallet = true }
.winkWallet(
isPresented: $showWallet,
accessToken: token
) { result in
print("Wallet: \(result.status ?? "")")
}
}
}
}
}Updated about 2 hours ago
