The SDK surfaces errors through the `checktiv.*.error` event. The `error` object on the event is a plain object with four fields:

```ts
{
  code: ChecktivErrorCode; // machine-readable discriminant
  message: string; // actionable, user-visible description
  recoverable: boolean; // true if the user can self-recover without a new session
  recovery: 'retry' | 'cross_device' | 'refresh_session' | 'contact_operator';
}
```

Discriminate on `error.code`. Do not use `instanceof`.

## Handling errors in your `onEvent` callback

```js
client.mountProvisioned({
  target: document.getElementById('idv-container'),
  onEvent: (event) => {
    if (event.type === 'checktiv.idv.error') {
      const { code, recovery, message } = event.error;
      // Handle specific error codes:
      switch (code) {
        case 'camera_denied':
          showCameraPermissionHelp();
          break;
        case 'session_expired':
          restartVerification(); // mint a new session and re-initialize
          break;
        default:
          showGenericErrorMessage(message);
      }
      // Handle recovery strategies separately from error codes.
      // 'refresh_session' is a recovery value, never an error code.
      if (recovery === 'refresh_session') {
        restartVerification(); // mint a new session and re-initialize
      }
    }
  },
});
```

## Recovery strategies

| Strategy           | Meaning                                                                   | Typical action                                                          |
| ------------------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `retry`            | The user can try the same step again without restarting.                  | Show the error message and a retry prompt.                              |
| `cross_device`     | The current device cannot complete capture; another device is needed.     | Offer a QR code or link to continue on a phone or desktop.              |
| `refresh_session`  | The session can no longer be used; your backend must mint a new one.      | Redirect to your session start flow or call your session-mint endpoint. |
| `contact_operator` | A configuration issue on the operator side; the user cannot self-recover. | Show support contact information.                                       |

## Error codes

### `origin_not_allowed`

**Cause:** The publishable key does not allow the current page origin. The SDK refuses to load because a request from an unregistered origin could be a third party embedding your key.

**Recovery:** `contact_operator` - the operator (you) must add the origin in the console under **Developers -> API keys**. The user cannot fix this. See [API keys](/developers/api-keys).

---

### `token_expired`

**Cause:** The `bt_*` browser token expired or the server returned 401. The SDK attempted one automatic refresh via `getSessionToken` but the retry also failed.

**Recovery:** `retry` - call `getSessionToken` again from your backend. If your mint endpoint is healthy, this usually resolves on the next attempt. If the session itself has expired, see `session_expired`.

---

### `session_expired`

**Cause:** The underlying verification session can no longer be used (expired, canceled, or already submitted).

**Recovery:** `refresh_session` - mint a new session on your backend and re-initialize the SDK with a fresh `bt_*`. Do not reuse the expired session ID.

---

### `protocol_mismatch`

**Cause:** The SDK version is incompatible with the version the capture frame expects. This usually means the SDK bundle is cached at an old version while the platform has moved forward.

**Recovery:** `contact_operator` - this is a version-floor failure that minting a new session cannot clear, so it is not a `refresh_session`. Update your SDK to the latest published version (and clear any stale CDN cache of the bundle). Until the integrated SDK version is updated, the user cannot self-recover.

---

### `camera_denied`

**Cause:** The browser denied camera access. The applicant blocked the camera permission prompt, or the site is not on HTTPS (required for camera access in all major browsers).

**Recovery:** `cross_device` (primary), `retry` (secondary) - show instructions for granting camera permission and offer a retry. If the applicant is on a desktop where camera permissions are tricky, offer a link to continue on a mobile device.

---

### `cv_gate_failed`

**Cause:** The image quality check failed. The captured image was too blurry, too dark, or the document was obscured.

**Recovery:** `retry` - the user can try again immediately. Show the `message` text (which contains coaching: better lighting, steady hand) and offer a retry button.

---

### `upload_failed`

**Cause:** A network error prevented the captured image from being uploaded. Also returned on rate limiting (the user has retried too many times in a short period).

**Recovery:** `retry` - check the connection and try again. If this is a rate-ceiling hit, the retry will succeed once the window resets. Show the `message` text and offer a retry.

---

### `sdk_load_failed`

**Cause:** The capture module could not load on this device. This can be caused by strict browser security settings, an ad blocker blocking the capture frame, or a device that lacks required browser APIs.

**Recovery:** `cross_device` - offer a link or QR code to continue on another device. The `message` text tells the user to try again or switch devices.

---

### `biometric_unsupported`

**Cause:** The device does not support the biometric capture required by the verification workflow (for example, no front-facing camera for selfie capture, or a browser that blocks camera APIs entirely).

**Recovery:** `cross_device` - the user must switch to a device that supports camera capture. Show the `message` text and offer a phone or tablet option.

---

### `isolation_required`

**Cause:** The embedding page configuration is not supported. The SDK needs to run in a context where it can open a sandboxed frame (for example, the page has `sandbox` attributes on its own iframe that block child iframes).

**Recovery:** `contact_operator` - this is an operator configuration issue. The user cannot fix it. Check that your page does not apply iframe sandbox restrictions that would block the capture frame.

---

## Related pages

- [Quickstart](/developers/sdks/quickstart) - see `onEvent` wiring end to end
- [React](/developers/sdks/react) - `onEvent` in the React wrapper
- [Verdict and webhooks](/developers/sdks/verdict-and-webhooks) - the authoritative outcome signal