Documentation menu
iOS
Drop-in identity verification for iOS: full-screen capture screens for the document and the selfie. Your backend creates the session; the SDK runs the document and liveness steps.
Prepare the environment
Add the prebuilt SDK to your Podfile and run pod install. It is a static framework,
so the app links its pods statically:
platform :ios, '15.5'
use_frameworks! :linkage => :static
target 'YourApp' do
pod 'BlinkKyc', :podspec => 'https://blink-pay.net/maven/ios/BlinkKyc-1.0.13.podspec'
endCocoaPods also brings in the SDK's own dependencies (TensorFlow Lite, NFCPassportReader and Lottie) at the
versions the framework was built with. Then add a camera usage description to Info.plist:
<key>NSCameraUsageDescription</key>
<string>We use the camera to photograph your document and confirm your identity.</string>Verification flow
Get a session token from your backend (Session API), build a
BlinkConfig, then present the capture screens from a view controller:
import BlinkKyc
func startVerification(sessionToken: String) {
Task {
do {
let config = try BlinkConfig.builder(sessionToken)
.baseUrl("https://kyc-api.blink-pay.net")
.issuingCountry("SDN") // SDN | ARE
.documentType(.PASSPORT) // .PASSPORT | .NATIONAL_ID
.build() // throws BlinkError (BLINK_CONFIG) on a bad value
let outcome = try await BlinkKyc(config)
.onProgress { step, state in print(step, state) }
.present(from: self) // the SDK owns the camera UI
// outcome.result: .VERIFIED | .REJECTED | .REVIEW, a device copy.
// Ask your backend for the authoritative result before acting.
} catch let error as BlinkStepError {
print("\(error.step): \(error.code)") // e.g. DOCUMENT_UNREADABLE
} catch let error as BlinkError {
print("\(error.code) (\(error.httpStatus))") // switch on error.code
} catch {
print(error)
}
}
}| Call | Effect |
|---|---|
BlinkConfig.builder(sessionToken) … .build() | The session settings:
baseUrl, issuingCountry (SDN or ARE, default SDN),
documentType (default .PASSPORT), profile, locale
("ar" for Arabic), theme, strings, rejectExpiredDocuments,
timeoutMs. build() throws. |
BlinkKyc(config) | Create the client. |
.document(type), .face() | Limit the journey to these steps. With neither, the
session's purpose decides. type must match the config. |
.onProgress { step, state in } | Progress events, e.g. DOCUMENT_FRONT UPLOADING. |
.present(from:) | Run the journey on the built-in capture screens: async throws,
or with a completion handler receiving Result<SessionResult, Error>. |
.capture(hooks).runAsync() | Supply the media yourself (headless). |
.status() | Read-only session progress from Blink. Blocking: call it off the main thread. |
Test on a real iPhone. The simulator has no camera, so the capture screens cannot run there.
Headless capture
Bring your own camera: return the document image for each side and the liveness frames, and the SDK handles challenges, uploads and the verdict. The hooks are called on a worker thread, so they may block.
final class MyCapture: CaptureHooks {
func document(type: DocumentType, side: DocumentSide) throws -> DocumentCapture {
DocumentCapture(image: try grabDocumentJpeg(side)) // FRONT, then BACK for an ID
}
func liveness(actions: [String]) throws -> LivenessCapture {
LivenessCapture(frames: try recordFrames(actions), actionWindows: nil)
}
}
BlinkKyc(config).capture(MyCapture()).runAsync()UI customisation
let config = try BlinkConfig.builder(sessionToken)
.theme(BlinkTheme.builder()
.accent(0xFF15803D)
.introColor(0xFF143A68)
.logo(BlinkImage.resource("BankLogo"))
.build())
.locale("ar") // Blink's built-in Arabic, right to left
.build()Every field and the copy you can replace are in Customisation.
Next
Handle the device outcome and errors (SDK result), then read the authoritative decision on your backend (Result API).