---
Source: https://docs.microblink.com/blinkcard/error-handling
Title: Handle errors and timeouts
Description: Handle scan timeouts and failures, and detect missing, unreadable, or blurry card fields with BlinkCard
---

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

<Tabs queryString="platform">
<TabItem value="web" label="Web (JS)" default>

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

```ts
blinkCard.addOnErrorCallback((error) => {
  if (error === "timeout") {
    // Prompt the user to try again in better conditions
  }
  void blinkCard.destroy();
});
```

</TabItem>
<TabItem value="android" label="Android (Kotlin)">

`BlinkCardCameraScanningScreen` invokes `onScanningCanceled` when the user cancels or dismisses scanning, including after a timeout dialog.
A successful scan instead calls `onScanningSuccess` with a `BlinkCardScanningResult`.

```kotlin
BlinkCardCameraScanningScreen(
    blinkCardSdk = blinkCardSdk,
    onScanningSuccess = { result ->
        // Use the scanning result
    },
    onScanningCanceled = {
        // No usable result — prompt the user to try again
    },
)
```

</TabItem>
<TabItem value="ios" label="iOS (Swift)">

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

```swift
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)
```

</TabItem>
</Tabs>

### Adjust the timeout

<Tabs queryString="platform">
<TabItem value="web" label="Web (JS)" default>

The scanning timeout defaults to 10 seconds.
Change it, or disable it, through the UX manager exposed on the component:

```ts
// Extend to 20 seconds
blinkCard.blinkCardUxManager.setTimeoutDuration(20000);

// Disable the timeout entirely
blinkCard.blinkCardUxManager.setTimeoutDuration(null);
```

</TabItem>
<TabItem value="android" label="Android (Kotlin)">

The step timeout defaults to 15 seconds.
Set it through `BlinkCardUxSettings`, in milliseconds.
A value of `0` disables the timeout.

```kotlin
BlinkCardCameraScanningScreen(
    blinkCardSdk = blinkCardSdk,
    uxSettings = BlinkCardUxSettings(stepTimeoutDurationMs = 20000),
    onScanningSuccess = { /* ... */ },
    onScanningCanceled = { /* ... */ },
)
```

</TabItem>
<TabItem value="ios" label="iOS (Swift)">

The step timeout defaults to 15 seconds.
Set `stepTimeoutDuration` (a `TimeInterval`, in seconds) on `BlinkCardSessionSettings`.

```swift
let sessionSettings = BlinkCardSessionSettings(stepTimeoutDuration: 20.0)
```

</TabItem>
</Tabs>

## 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](./scan-image.md), the process result carries a `resultCompleteness` object with one status per field.

<Tabs queryString="platform">
<TabItem value="web" label="Web (JS)" default>

```ts
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

</TabItem>
<TabItem value="android" label="Android (Kotlin)">

```kotlin
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 successfully
- `NotExtracted`: the field is present but couldn't be read—a blocking error that needs user action
- `NotPresent`: the field hasn't been seen on any scanned side yet
- `NotRequested`: extraction was disabled for this field
- `NotAvailable`: no status is available

</TabItem>
<TabItem value="ios" label="iOS (Swift)">

```swift
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 successfully
- `notExtracted`: the field is present but couldn't be read—a blocking error that needs user action
- `notPresent`: the field hasn't been seen on any scanned side yet
- `notRequested`: extraction was disabled for this field
- `notAvailable`: no status is available

</TabItem>
</Tabs>

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

## Related articles

- [Scan a card via camera](./scan-camera.md)
- [Scan a card from a static image](./scan-image.md)


Last updated on Jun 18, 2026
