Enrollment Orchestration
This page explains how to orchestrate enrollment with mobileID.enrollment, resumable flow.
What Enrollment Orchestration Covers
Use the enrollment API to:
- retrieve available enrollment candidates,
- start a new enrollment (remote or activation-token based),
- resume an interrupted in-progress enrollment,
- handle enrollment-related deep links (for example phone OTP binding),
- react to busy/idle UX signals and custom plugin view hooks,
- handle terminal outcomes (success, cancel, retryable failure, interruption).
Core API Surface
The orchestration entry point is mobileID.enrollment:
candidates() async throws -> [EnrollmentCandidate]canResume() async -> EnrollmentResumptionStatusstart(with:in:delegate:) async -> Result<CredentialUpdateContext, EnrollmentProcessFailure>resume(with:in:delegate:) async -> Result<CredentialUpdateContext, EnrollmentProcessFailure>handle(deepLink:) -> Boolcancel() asynctransactionData: EnrollmentTransactionData?
Available Enrollment Plugins
The SDK resolves enrollment steps through EnrollmentPlugin classes.
There are two groups:
- integrator-configurable plugins (provided in
MobileIDConfiguration.enrollmentPlugins), - internal plugins (always added by the SDK engine).
Integrator-Configurable Plugins
Plugin Class | Module | What it does |
|---|---|---|
EnrollmentPluginPhoneInfo | MobileIDEnrollmentPluginPhoneBinding | Collects and submits the user phone number for binding (PHONE_INFO). |
EnrollmentPluginPhoneOtp | MobileIDEnrollmentPluginPhoneBinding | Verifies the phone ownership with OTP and completes phone binding (PHONE_OTP). |
EnrollmentPluginEmailInfo | MobileIDEnrollmentPluginEmailBinding | Collects and submits the user email for email binding (EMAIL_INFO). |
EnrollmentPluginDocumentAuthentication | MobileIDEnrollmentPluginDocumentAuthentication | Runs local document capture/authentication and submits captured document pages (DOC_AUTH / WEBBIO_CODING). |
EnrollmentPluginRemoteDocumentCapture | MobileIDEnrollmentPluginDocumentAuthentication | Runs remote/live document capture session flow (DOCUMENT_LIVE_CAPTURE). |
EnrollmentPluginMrzScanning | MobileIDEnrollmentPluginDocumentAuthentication | Captures and submits MRZ data used in ICAO/NFC-assisted document flows (NFC_MRZ). |
EnrollmentPluginNfcScanning | MobileIDEnrollmentPluginDocumentAuthentication | Performs NFC chip read and validates document chip data (NFC_PHASE). |
EnrollmentPluginFaceMatching | MobileIDEnrollmentPluginFaceMatching | Captures selfie/liveness data and submits face matching payload (FACE_MATCHING). |
EnrollmentPluginReferenceSelfie | MobileIDEnrollmentPluginFaceMatching | Submits previously stored reference selfie for matching (MID_SELFIE_MATCHING). |
EnrollmentPluginLivenessChallengeSessionCreation | MobileIDEnrollmentPluginFaceMatching | Creates a remote liveness challenge session (LIVENESS_CHALLENGE_SESSION_CREATION). |
EnrollmentPluginLivenessChallengePhase | MobileIDEnrollmentPluginFaceMatching | Executes remote liveness challenge capture phase (LIVENESS_CHALLENGE_PHASE). |
EnrollmentPluginAttributeMatching | MobileIDEnrollmentPluginDocumentAuthentication | Handles backend-driven attribute matching expected data branch (ATTRIBUTE_MATCHING). |
End-To-End Flow
Start A Remote Enrollment
Swift1import MobileIDCore2import UIKit34@MainActor5final class EnrollmentCoordinator: NSObject, EnrollmentInteractionDelegate {6 private let mobileID: MobileID7 private let navigationController: UINavigationController89 init(mobileID: MobileID, navigationController: UINavigationController) {10 self.mobileID = mobileID11 self.navigationController = navigationController12 }1314 func startRemoteEnrollment() async {15 do {16 let candidates = try await mobileID.enrollment.candidates()17 guard let candidate = candidates.first(where: { $0.isRemoteEnrollmentAvailable }) else {18 // No remotely enrollable credential available for this user.19 return20 }2122 let params = EnrollmentParameters(23 enrollmentType: .remote(candidate: candidate),24 customSteps: [],25 authenticatorBiometricProtection: .strong26 )2728 let result = await mobileID.enrollment.start(29 with: params,30 in: navigationController,31 delegate: self32 )3334 await handle(result)35 } catch {36 // candidates() failed (network/config/runtime).37 }38 }3940 private func handle(_ result: Result<CredentialUpdateContext, EnrollmentProcessFailure>) async {41 switch result {42 case .success(let context):43 let credential = context.credential44 print("Enrollment succeeded: \(credential)")4546 case .failure(.cancelled):47 print("User cancelled enrollment")4849 case .failure(.interrupted):50 print("Enrollment interrupted by another enrollment start")5152 case .failure(.failed(let error)):53 print("Non-retryable enrollment error: \(error)")5455 case .failure(.failedWithRetryPossibility(let error, let onRetry)):56 print("Retryable error: \(error)")57 let retryResult = await onRetry()58 await handle(retryResult)59 }60 }6162 func enrollmentDidStart(timestamp: Date) {}6364 func enrollmentDidBecomeBusy(expectedInterval: NavigatorBusyStateInterval) {65 // Show loader/progress UI.66 }6768 func enrollmentDidBecomeIdle() {69 // Hide loader/progress UI.70 }7172 func enrollmentView(for context: EnrollmentViewContext) -> UIViewController? {73 // Optional: return a custom view controller for plugin-defined interaction points.74 // Call context.onContinue(nil) when user confirms, or context.onContinue(error) to abort.75 nil76 }77}
Resume An In-Progress Enrollment
Swift1@MainActor2func resumeIfNeeded(mobileID: MobileID, navigationController: UINavigationController, delegate: EnrollmentInteractionDelegate) async {3 let status = await mobileID.enrollment.canResume()45 guard case .canResume = status else {6 return7 }89 let result = await mobileID.enrollment.resume(10 with: [],11 in: navigationController,12 delegate: delegate13 )1415 // Handle result with the same success/failure logic as start().16 _ = result17}
Activation-Token Enrollment + Deep Links
DeepLink(url:) can parse SDK links. For enrollment orchestration, the two relevant branches are:
.enrollment(payload:): use this to buildActivationTokenand start token-based enrollment,.phoneBinding(otp:): forward tomobileID.enrollment.handle(deepLink:)so phone OTP can be consumed by the active step.
Swift1func application(2 _ app: UIApplication,3 open url: URL,4 options: [UIApplication.OpenURLOptionsKey: Any] = [:]5) -> Bool {6 guard let deepLink = DeepLink(url: url) else { return false }78 switch deepLink {9 case .phoneBinding:10 return mobileID.enrollment.handle(deepLink: deepLink)1112 case .enrollment:13 do {14 let token = try ActivationToken(deepLink: deepLink)15 let params = EnrollmentParameters(16 enrollmentType: .activationToken(token),17 authenticatorBiometricProtection: .strong18 )1920 Task { @MainActor in21 _ = await mobileID.enrollment.start(with: params, in: rootNavigationController, delegate: enrollmentDelegate)22 }23 return true24 } catch {25 return false26 }2728 default:29 // qrCode/mdoc deep links are handled by other SDK domains.30 return false31 }32}
Custom Step Integration (Advanced)
Use CustomStep when your backend process includes an expected data key that must be fulfilled by app-owned logic.
Swift1import MobileIDCommonUtils2import MobileIDCore34struct TermsAcceptanceSubmission: EnrollmentSubmission {5 let path = "transactions/{transactionId}/terms-acceptance"6 let body: SubmissionBody78 init(version: String, accepted: Bool) {9 body = SubmissionBody([10 "version": version,11 "accepted": accepted12 ])13 }14}1516final class TermsAcceptanceStep: CustomStep {17 let key = "TERMS_ACCEPTANCE"1819 func onStep(navigator: Navigator) async throws -> EnrollmentSubmission {20 // Present your own UI, collect consent, then submit payload.21 return TermsAcceptanceSubmission(version: "2026-01", accepted: true)22 }2324 func cancelRequested() {25 // Optional: cancel internal tasks and make onStep throw CancellationError.26 }27}
Operational Guidance
- Always call
canResume()on app launch or when returning to your enrollment entry screen. - Keep a single enrollment at a time; starting while one is active returns an active-enrollment failure.
- Route deep links early (App/Scene delegate) and pass phone binding links to
handle(deepLink:)immediately. - Treat
.failedWithRetryPossibilityas a user-recovery path and preserve context for retry. - Use
transactionDataonly as transient runtime data for an active process. - Keep
enrollmentPluginsaligned with backend-enabled workflow steps.
Related APIs
EnrollmentParametersEnrollmentType(.remote,.activationToken)EnrollmentProcessFailureEnrollmentInteractionDelegateCustomStep/EnrollmentSubmission