Handle errors and timeouts
This guide shows how to handle scanning that fails or times out, and how to tell when individual fields couldn't be read.
Handle scan failures and timeouts
When you use the camera UI, the SDK exposes a single entry point for a session that finishes without a usable result. A finished-but-empty result generally means the user should retry.
- Web (JS)
- Android (Kotlin)
- iOS (Swift)
Register an error callback. It receives one of a small set of error states, and these errors generally mean the user should retry:
"timeout": scanning ran longer than the allowed duration without completing"result_retrieval_failed": the result could not be retrieved"unknown": an unexpected error
blinkCard.addOnErrorCallback((error) => {
if (error === "timeout") {
// Prompt the user to try again in better conditions
}
void blinkCard.destroy();
});
BlinkCardCameraScanningScreen invokes onScanningCanceled when the user cancels or dismisses scanning, including after a timeout dialog.
A successful scan instead calls onScanningSuccess with a BlinkCardScanningResult.
BlinkCardCameraScanningScreen(
blinkCardSdk = blinkCardSdk,
onScanningSuccess = { result ->
// Use the scanning result
},
onScanningCanceled = {
// No usable result — prompt the user to try again
},
)
BlinkCardUXModel publishes the outcome through its @Published result: BlinkCardResultState? property.
Observe $result: when scanning ends without a usable capture, the model emits a BlinkCardResultState whose scanningResult is nil.
uxModel.$result
.compactMap { $0 }
.sink { state in
guard state.scanningResult != nil else {
// No usable result — prompt the user to try again
return
}
// Use state.scanningResult
}
.store(in: &cancellables)
Adjust the timeout
- Web (JS)
- Android (Kotlin)
- iOS (Swift)
The scanning timeout defaults to 10 seconds. Change it, or disable it, through the UX manager exposed on the component:
// Extend to 20 seconds
blinkCard.blinkCardUxManager.setTimeoutDuration(20000);
// Disable the timeout entirely
blinkCard.blinkCardUxManager.setTimeoutDuration(null);
The step timeout defaults to 15 seconds.
Set it through BlinkCardUxSettings, in milliseconds.
A value of 0 disables the timeout.
BlinkCardCameraScanningScreen(
blinkCardSdk = blinkCardSdk,
uxSettings = BlinkCardUxSettings(stepTimeoutDurationMs = 20000),
onScanningSuccess = { /* ... */ },
onScanningCanceled = { /* ... */ },
)
The step timeout defaults to 15 seconds.
Set stepTimeoutDuration (a TimeInterval, in seconds) on BlinkCardSessionSettings.
let sessionSettings = BlinkCardSessionSettings(stepTimeoutDuration: 20.0)
Detect missing or unreadable fields
When scanning completes, check the per-field extraction status to tell apart a field that was never present from one that was seen but couldn't be read.
In a photo flow, the process result carries a resultCompleteness object with one status per field.
- Web (JS)
- Android (Kotlin)
- iOS (Swift)
const processResult = session.process(imageData);
const completeness = processResult.resultCompleteness;
if (completeness.cvvExtractionStatus === "not-extracted") {
// The CVV was visible but couldn't be read (blur, glare, occlusion) — ask for a clearer capture
}
if (completeness.cvvExtractionStatus === "not-present") {
// The CVV hasn't appeared yet — it may be on a side not scanned so far
}
Each field status is one of:
"extracted": the field was read successfully"not-extracted": the field is present but couldn't be read—a blocking error that needs user action"not-present": the field hasn't been seen on any scanned side yet"not-requested": extraction was disabled for this field"not-available": no status is available
val processResult = session.process(inputImage)
val completeness = processResult.resultCompleteness
when (completeness.cvvExtractionStatus) {
FieldExtractionStatus.NotExtracted -> {
// The CVV was visible but couldn't be read (blur, glare, occlusion) — ask for a clearer capture
}
FieldExtractionStatus.NotPresent -> {
// The CVV hasn't appeared yet — it may be on a side not scanned so far
}
else -> { /* ... */ }
}
Each field status is a FieldExtractionStatus value:
Extracted: the field was read successfullyNotExtracted: the field is present but couldn't be read—a blocking error that needs user actionNotPresent: the field hasn't been seen on any scanned side yetNotRequested: extraction was disabled for this fieldNotAvailable: no status is available
let frameResult = try await session.process(inputImage: inputImage)
let completeness = frameResult.processResult?.resultCompleteness
switch completeness?.cvvExtractionStatus {
case .notExtracted:
// The CVV was visible but couldn't be read (blur, glare, occlusion) — ask for a clearer capture
case .notPresent:
// The CVV hasn't appeared yet — it may be on a side not scanned so far
default:
break
}
Each field status is a FieldExtractionStatus value:
extracted: the field was read successfullynotExtracted: the field is present but couldn't be read—a blocking error that needs user actionnotPresent: the field hasn't been seen on any scanned side yetnotRequested: extraction was disabled for this fieldnotAvailable: no status is available
Handle blur and poor lighting
By default, BlinkCard rejects blurred frames so it only processes sharp images, which already guards against most low-quality captures.
If a scan keeps timing out because the input is consistently blurry, the most useful response is to guide the user: improve lighting, hold the card flat, and avoid glare. The built-in camera UI already shows this kind of real-time feedback.