Wink Identity Mobile SDK


Wink iOS SDK Integration Guide

Requirements

RequirementMinimum
iOS Deployment Target14.0
Swift5.9+
Xcode15.0+

Installation

Swift Package Manager (SPM)

  1. In Xcode, go to File > Add Package Dependencies...
  2. Enter the Wink SDK SPM distribution repository URL.
  3. Select the version rule and add the WinkFlutterSDK library 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>'
end

Then run:

pod install

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

ParameterTypeDefaultDescription
keyIdStringrequiredYour Wink API key ID
merchantIdStringrequiredYour merchant identifier
environmentString"dev""dev", "staging", or "prod"
livenessEnabledBoolfalseEnable face liveness detection
faceConfidenceThresholdInt90Face match confidence threshold (0-100)
mfaOrder[String][]MFA method priority order
retriesAllowedInt0Number of biometric retries before failure
presentExtProfileBoolfalseShow extended profile fields
enablePalmBoolfalseEnable palm biometric
allowPalmFlipBoolfalseAllow palm flip gesture
palmCameraDirInt1Palm camera direction
auth0DomainString?nilAuth0 tenant domain
auth0ClientIdString?nilAuth0 client ID
allowAppleLoginBoolfalseEnable Sign in with Apple
allowGoogleLoginBoolfalseEnable Google Sign-In
allowPazeLoginBoolfalseEnable Paze login
iamFirstBoolfalseShow IAM login before biometrics
iamFirstProviderString?nilRequired when iamFirst is true
allowIdScanBoolfalseEnable ID document scanning
enableVoiceBoolfalseEnable voice biometric
enableDocumentVerificationBoolfalseEnable document verification
signingKeyResourceString?nilBundle resource name for signing PEM (without .pem extension)
signingKeyBundleBundle.mainBundle containing the signing key
autoPreWarmBooltruePre-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.

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
messageString?Error message (prefixed with wink_ on SDK errors)
isSuccessBooltrue if no wink_* error occurred

WinkPaymentResult

Returned by start and openWallet.

PropertyTypeDescription
statusString?Payment status
transactionIdString?Transaction identifier
messageString?Error message
raw[String: Any]Full raw payload
isSuccessBooltrue if no wink_* error occurred

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 nil when false

Runtime Errors

WinkSDKError is thrown by embedded VC factory methods:

ErrorDescription
.notConfiguredconfigure() 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:

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.


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