The SDK surfaces errors through the checktiv.*.error event. The error object on the event is a plain object with four fields:
{ code: ChecktivErrorCode; // machine-readable discriminant, forward-open 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
Section titled “Handling errors in your onEvent callback”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: // Required: also reached by a code added in a later minor release. 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
Section titled “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. |
The processing signal (checktiv.idv.processing)
Section titled “The processing signal (checktiv.idv.processing)”checktiv.idv.processing is not an error. It arrives on onEvent as a checktiv.idv.processing event (never as checktiv.idv.error) and means the journey is alive but there is nothing for the applicant to do right now. It carries an optional reason that tells you how to respond, because the two cases need OPPOSITE handling:
reason |
Meaning | What to do |
|---|---|---|
signal_pending |
The submit reached the backend, but the server has not finished recording it yet. This resolves on its own. | Reload into your processing screen. A reload progresses the journey. |
no_renderable_step |
The current step runs entirely on the server (for example, a background check) and has no applicant screen. | Render a TERMINAL processing screen. Do NOT reload. |
onEvent: (event) => { if (event.type === 'checktiv.idv.processing') { if (event.reason === undefined || event.reason === 'signal_pending') { // 'signal_pending', or a missing reason (the legacy signal-pending case): // the only reloadable case. window.location.reload(); // safe: the reload progresses the journey } else { // 'no_renderable_step' or any unrecognized future reason: prefer the // terminal processing surface, which never loops. showProcessingScreen(); // terminal: do not reload } }};reason is optional and forward-open. A handler that reads only event.type keeps working. Treat a missing reason as signal_pending (the legacy behavior, safe to reload). For an unrecognized future reason, prefer the terminal processing surface, which never loops.
Error codes
Section titled “Error codes”The causes and recovery advice below are written for the identity-capture module, which is where most of these codes originate. Four of them are shared with the credit-history module, and there the cause is different: sdk_load_failed, session_expired, submit_failed and origin_not_allowed all carry credit-specific message text that names the right next step. Render error.message and error.recovery rather than a sentence of your own keyed on the code, and see Credit history for what each one means on that module.
origin_not_allowed
Section titled “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.
token_expired
Section titled “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
Section titled “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.
wrong_token_type
Section titled “wrong_token_type”Cause: A client token (or link token) was handed to the browser data plane instead of a browser token. getSessionToken must resolve to a browser token (bt_*). When it returns a client/link token, the browser data plane is served the first-party journey response shape the embedded SDK cannot read, and the module surfaces this typed error instead of an opaque session_expired. This is an integration-time error, not an applicant condition: the applicant sees a generic “could not start” message while the actionable developer detail is logged to the browser console.
Recovery: contact_operator - fix the integration, not the session. Either return a browser token (bt_*) from getSessionToken (mint it on your backend with POST /v1/sessions/{id}/browser_token), or use Checktiv.mount() with a client token so the SDK performs the token exchange internally. See Quickstart and Token handoff.
protocol_mismatch
Section titled “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
Section titled “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 - the recovery field is cross_device. Show instructions for granting camera permission (after which the applicant can retry the same step), and offer a link to continue on a mobile device where camera permissions are simpler.
camera_policy_blocked
Section titled “camera_policy_blocked”Cause: Your page sends a Permissions-Policy header whose camera allowlist does not reach the verification frame, so the browser refuses the frame access to the camera. Two shapes do that: an allowlist that leaves the capture origin out, and an allowlist that names the capture origin but drops self, which strips your own page of the camera grant it would have delegated. The SDK reads the frame’s own camera permission as the frame starts up; when capture then fails, it reports this code instead of the device-fault code the failure would otherwise carry. The common variant is an allowlist that names the CDN origin the SDK script is served from instead of the capture origin the frame runs on: Permissions Policy matches the frame’s own document origin, never the origin its scripts came from.
A page that declares no camera directive at all is not affected and does not produce this code: the frame receives the camera through the allow="camera" attribute the SDK sets. Only a declared allowlist causes it: one that omits the capture origin, one that omits self, or an empty one (camera=()).
This is distinct from camera_denied (the applicant blocked the permission prompt) and from camera_unsupported (the device’s camera cannot meet the minimum capture resolution). Both of those are the applicant’s device. This one is your page.
Recovery: contact_operator - only the operator (you) can fix it, by allowing both self and the capture origin in the header your page serves. Deliberately not a cross_device recovery: your page sends the same header to the applicant’s second device, so offering another device is a guaranteed dead end. The browser console carries the developer detail (the capture origin the frame needs, what your camera allowlist currently contains, and the header line to add); the message on the error is applicant-facing and names none of it. See Security headers for the origins per region and copy-pasteable snippets.
camera_unsupported
Section titled “camera_unsupported”Cause: The device has a camera, but it does not meet the minimum resolution the capture step requires. This is distinct from camera_denied (a blocked permission) and from biometric_unsupported (a page-configuration issue the applicant cannot resolve by switching devices).
Recovery: cross_device - the camera on this device cannot produce a usable capture, so the applicant should continue elsewhere. Offer a link or QR code to continue on a phone. The message text tells the applicant their camera does not meet the minimum resolution and to try another device.
cv_gate_failed
Section titled “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
Section titled “upload_failed”Cause: A network error prevented the captured image from reaching storage. This covers the image upload only. When the images uploaded and the step submit failed, the SDK returns submit_failed instead. 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.
submit_failed
Section titled “submit_failed”Cause: The captured images reached storage, but the step submit that follows them did not complete. The capture itself is good, so the applicant does not need to capture anything again.
Recovery: retry - the captured images are preserved, so offer a retry of the submit. Show the message text and a retry control. Do not send the applicant back to the camera: a re-capture is not needed and discards a valid capture.
On the credit-history module there are no captured images and nothing the applicant entered has been sent. The recovery is the same shape, a retry of the submit, but the reason to show the applicant is different, which is why that module overrides the message. Do not tell a credit applicant their answers are preserved on the server: they are not, and the module’s own message says so correctly.
sdk_load_failed
Section titled “sdk_load_failed”Cause: A module the session needs 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.
It can also be your page’s own Permissions-Policy header. Where the SDK cannot run the camera-policy pre-flight (see camera_policy_blocked), a missing camera grant lands here instead, because through the camera APIs a page-policy block is indistinguishable from a device that has no camera. So this code does not prove a device fault. If it reproduces on every device you try, check your header before you investigate anything else: Security headers.
On a script-tag install it can also be your Content-Security-Policy. Each module is a separate file the SDK fetches from the CDN origin at the step that needs it, so a script-src that does not name that origin lets your <script> tag load and blocks the module behind it. Like the header cause above, this reproduces on every device. See Security headers.
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. That advice is right for the load-failure causes above and wrong for either header cause, since your page serves the same headers to the second device, which is why the camera case has its own code wherever the SDK can detect it.
biometric_unsupported
Section titled “biometric_unsupported”Cause: The biometric capture the workflow requires is not supported in this page configuration. This is a terminal configuration state, not a device-capability issue the applicant can resolve by switching devices.
Recovery: contact_operator - the applicant cannot self-recover. The message text tells the applicant this capture is not supported on this page and to contact the site operator. When a device simply has a camera that cannot meet the minimum capture resolution, the SDK surfaces camera_unsupported (recovery cross_device) instead, so the applicant can move to a phone.
isolation_required
Section titled “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
Section titled “Related pages”- Quickstart - see
onEventwiring end to end - React -
onEventin the React wrapper - Verdict and webhooks - the authoritative outcome signal
- Credit history - the four codes the credit module reports, and the three outcomes that are not errors