JavaScript SDK 3.2.0.0 Guide
# SDK response update 3.2.0.0
Version 3.2.0.0 keeps the **standardized response object introduced in 3.1.0.0** (`processType`, `statusCode`, `detail`) and adds reliability and observability features around it: a global timeout, automatic camera pre-checks, protection against concurrent calls, configuration prefetch and real-time lifecycle events. Integrations that already handle 3.1.0.0 responses keep working; this page lists every behavior you should review when upgrading.
## What's new in 3.2.0.0
| Area | Change |
|---|---|
| Timeout | Global timeout (default and **minimum 90 s**). On expiry the call fails with `TIMEOUT` (`statusCode: 3`). 3.1.0.0 had no SDK-level timeout. |
| Pre-checks | Before opening the camera the SDK verifies browser support and that a video device exists. Failures return `NOT_SUPPORTED` / `CAMERA_NOT_FOUND` with `processType: "config"`. |
| Concurrency | A second `initLiveness` / `initCardCapture` call while another is running is rejected immediately with `SDK_ERROR`. |
| `prefetch()` | Pre-loads and caches the configuration so the camera opens faster. |
| `onEvent` | Real-time lifecycle events (camera ready, processing, timeouts…). |
| New reasons | `CLOSED_EYES`, `NOT_ALIVE`, `CONNECTION_ERROR`. |
| Liveness result | A liveness result without a `reason` is now treated as approved (`NOT_ALERT`). In 3.1.0.0 it was returned as rejected (`UNKNOWN`). |
| Texts | `reason.description` is mostly in Spanish (see [Reason catalog](#liveness-reasons)). **Always branch on `reason.name`, never on `description`.** |
| Card capture | The SDK requests the cropped document image for each side. |
## Loading and initializing the SDK
```html
<script src="<CDN_BASE>/3.2.0.0/ComponentsManager.js"></script>
```
```javascript
const sdk = new AdoSDK(urlBase, projectName, apiKey, productId, {
timeout: 120000, // optional, ms. Default 90000. Minimum 90000
cacheTTL: 30000 // optional, ms. Default 30000
});
```
| Constructor option | Type | Default | Notes |
|---|---|---|---|
| `timeout` | number (ms) | `90000` | Values below `90000` are raised to `90000`. `0` or invalid values fall back to the default. |
| `cacheTTL` | number (ms) | `30000` | How long the configuration obtained by `prefetch()` / a previous call is reused. |
The constructor throws a regular `Error` (`"AdoSDK: missing required params: …"`) if `urlBase`, `projectName`, `apiKey` or `productId` is missing.
```javascript
// Liveness
const result = await sdk.initLiveness({ processId, deviceId, debug, onEvent });
// Card capture. singleFlow defaults to true (both sides in one call)
const result = await sdk.initCardCapture(isFrontSide, { processId, singleFlow, debug, onEvent });
```
| Call option | Description |
|---|---|
| `processId` | Your own identifier for the process (GUID recommended). Returned as `detail.processId` and used to correlate webhooks. If omitted, `detail.processId` is `null`. |
| `deviceId` | Optional device identifier. If omitted the SDK generates one. Returned as `detail.uidDevice`. |
| `debug` | `true` adds a `debug` object (`config`, `logs`) to the response. |
| `onEvent` | Lifecycle callback (see [Lifecycle events](#lifecycle-events-onevent)). |
| `singleFlow` | Card capture only. `true` (default): front and back in one call. `false`: one side per call (`isFrontSide` selects it). |
## Response object
```json
{
"processType": "liveness",
"statusCode": 1,
"detail": {
"status": "approved",
"reason": {
"name": "NOT_ALERT",
"description": "The transaction was approved with no alerts detected"
},
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12",
"keyProcessLiveness": "69c265aeca9d4863b61b1fc6a8099f52",
"image": "<BASE64>"
}
}
```
| Field | Type | Description |
|---|---|---|
| `processType` | string | `"liveness"`, `"cardcapture"`, or `"config"` (error before the flow starts: configuration, camera pre-checks). |
| `statusCode` | number | `1` approved · `2` rejected · `3` failed. |
| `side` | string | **Card capture only. Root level** (not inside `detail`). `"front"`, `"back"` or `"both"`. Never present in liveness. |
| `detail.status` | string | `"approved"`, `"rejected"` or `"failed"` (text equivalent of `statusCode`). |
| `detail.reason.name` | string | Constant code of the outcome (e.g. `NOT_ALERT`, `SPOOFING`, `TIMEOUT`). **Use this field in your logic.** |
| `detail.reason.description` | string | Human-readable text. May change between versions or builds; do not parse it. |
| `detail.uidDevice` | string | Device identifier (yours via `deviceId`, or generated). |
| `detail.processId` | string \| null | The `processId` you passed in the call (`null` if omitted). |
| `detail.keyProcessLiveness` | string | **Liveness only.** Liveness process key (present in every liveness response). |
| `detail.image` | string | **Liveness only.** Base64-encoded face image, without the `data:` prefix. Present on results returned by the liveness service (approved and rejected). |
| `detail.front` / `detail.back` | object | **Card capture, approved only.** `{ image: { encoding, mimeType, data } }`. See [Card capture](#card-capture). |
| `debug` | object | Only when `debug: true`: `{ config, logs }`. |
There is no `reason.code` in SDK responses. (Webhook payloads do include a `reason.code`; see [Webhooks](#webhooks).)
## How results are delivered
Rejected and failed results that stop the flow are delivered by **rejecting the promise**, with an object in the same format as a successful response. Wrap calls in `try/catch`.
| Process | Promise resolves | Promise rejects (`catch`) |
|---|---|---|
| Liveness | `statusCode 1` and `statusCode 2` (result evaluated by the liveness service) | `statusCode 3` (cancel, timeout, camera permission, internal error) and `VALIDATION_FAILED` (`statusCode 2`) |
| Card capture | `statusCode 1` | `statusCode 2` and `statusCode 3` |
| Errors before the flow (`processType: "config"`) | — | Always |
```javascript
try {
const result = await sdk.initLiveness({ processId });
if (result.statusCode === 1) {
// approved (NOT_ALERT)
} else {
// statusCode 2: not approved by the liveness service
// result.detail.reason.name → SPOOFING | PAD | CLOSED_EYES | NOT_ALIVE | FACE_NOT_DETECTED | UNKNOWN
}
} catch (err) {
if (err && err.detail) {
// same structure as a response: err.statusCode (2 | 3), err.detail.reason.name
switch (err.detail.reason.name) {
case 'TIMEOUT': /* time expired */ break;
case 'USER_CANCELED': /* user closed the component */ break;
case 'NOT_SUPPORTED': /* browser without camera support */ break;
case 'CAMERA_NOT_FOUND': /* no camera available */ break;
default: /* log err.detail.reason */
}
} else {
// not an SDK result (e.g. constructor validation error)
}
}
```
## Liveness
### Liveness reasons
`description` is shown exactly as returned by 3.2.0.0.
| `reason.name` | `statusCode` | Delivered by | `description` | Meaning |
|---|---|---|---|---|
| `NOT_ALERT` | 1 | resolve | The transaction was approved with no alerts detected | Approved, no alerts. (`SUCCESS`, "Operación exitosa", is also treated as approved.) |
| `SPOOFING` | 2 | resolve | Intento de suplantación detectado | Spoofing attempt detected. |
| `PAD` | 2 | resolve | Presentation attack detected | Presentation attack detected. |
| `CLOSED_EYES` | 2 | resolve | Ojos cerrados detectados | Eyes detected as closed during capture. |
| `NOT_ALIVE` | 2 | resolve | No se detectó persona viva | The subject did not pass the liveness check. |
| `FACE_NOT_DETECTED` | 2 | resolve | Rostro no detectado | No face detected in the frame. |
| `UNKNOWN` | 2 | resolve | Error desconocido | Any other reason returned by the liveness service. |
| `VALIDATION_FAILED` | 2 | **reject** | Error de validación en liveness | The result could not be validated. |
| `USER_CANCELED` | 3 | reject | Usuario canceló el proceso | The user closed the liveness component. |
| `TIMEOUT` | 3 | reject | Tiempo agotado | Global timeout reached, or timeout reported by the liveness component. |
| `CAMERA_DENIED` | 3 | reject | Permiso de cámara denegado | Camera permission denied. |
| `INTERNAL_ERROR` | 3 | reject | Error interno del sistema | Unexpected error in the flow. |
### Examples
**Approved** (`statusCode: 1`) — see [Response object](#response-object).
**Rejected** (`statusCode: 2`, resolves)
```json
{
"processType": "liveness",
"statusCode": 2,
"detail": {
"status": "rejected",
"reason": { "name": "SPOOFING", "description": "Intento de suplantación detectado" },
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12",
"keyProcessLiveness": "69c265aeca9d4863b61b1fc6a8099f52",
"image": "<BASE64>"
}
}
```
**Failed — timeout** (`statusCode: 3`, rejects; no `image`)
```json
{
"processType": "liveness",
"statusCode": 3,
"detail": {
"status": "failed",
"reason": { "name": "TIMEOUT", "description": "Tiempo agotado" },
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12",
"keyProcessLiveness": "69c265aeca9d4863b61b1fc6a8099f52"
}
}
```
## Card capture
`singleFlow: true` (default) captures front and back in one call and returns `side: "both"`. With `singleFlow: false`, each call captures one side and returns `side: "front"` or `"back"`.
The captured image of each side is the **cropped document image**, inside `detail.front` / `detail.back` (only in approved results):
```json
"front": { "image": { "encoding": "base64", "mimeType": "image/jpeg", "data": "<BASE64>" } }
```
Rejected and failed results contain **no images**. `side` on those results is the side that was being captured when the event happened.
### Card capture reasons
| `reason.name` | `statusCode` | Delivered by | Meaning |
|---|---|---|---|
| `SUCCESS` | 1 | resolve | Document captured. `description`: "Operación exitosa". |
| `WRONG_SIDE` | 2 | **reject** | The document side shown is not the one requested. |
| `USER_CANCELED` | 3 | reject | The user closed the component. |
| `TIMEOUT` | 3 | reject | Global timeout reached (see [Timeouts](#timeouts)), or the capture component reported a scan timeout. |
| `SDK_ERROR` | 3 | reject | Internal error reported by the capture component. `description` may carry the technical detail. |
| `INTERNAL_ERROR` | 3 | reject | Failure that does not match a known reason (for example, the document image could not be extracted). |
| `UNKNOWN` | 2 | reject | Rejection that does not match a known reason. |
The following names are also recognized and returned as-is if the capture component reports them: `PERMISSION_DENIED`, `CAMERA_NOT_FOUND`, `CAMERA_IN_USE`, `NOT_SUPPORTED`, `DOCUMENT_NOT_DETECTED`, `NETWORK_ERROR`, `FOCUS_LOST`. Keep handling them for forward compatibility; their `statusCode` is the one reported by the capture component (normally `3`).
> For card capture, `reason.description` can come from the capture component itself (English or Spanish). Do not compare it with fixed texts.
### Examples
**Approved, both sides** (`side: "both"`, resolves)
```json
{
"processType": "cardcapture",
"statusCode": 1,
"side": "both",
"detail": {
"status": "approved",
"reason": { "name": "SUCCESS", "description": "Operación exitosa" },
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12",
"front": { "image": { "encoding": "base64", "mimeType": "image/jpeg", "data": "<BASE64>" } },
"back": { "image": { "encoding": "base64", "mimeType": "image/jpeg", "data": "<BASE64>" } }
}
}
```
**Approved, one side** (`singleFlow: false`) — same structure with `side: "front"` and only `detail.front` (or `side: "back"` and only `detail.back`).
**Rejected — wrong side** (`statusCode: 2`, rejects)
```json
{
"processType": "cardcapture",
"statusCode": 2,
"side": "front",
"detail": {
"status": "rejected",
"reason": { "name": "WRONG_SIDE", "description": "Lado incorrecto del documento." },
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12"
}
}
```
**Failed — user canceled** (`statusCode: 3`, rejects)
```json
{
"processType": "cardcapture",
"statusCode": 3,
"side": "front",
"detail": {
"status": "failed",
"reason": { "name": "USER_CANCELED", "description": "The user canceled the process before completion." },
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12"
}
}
```
## Errors before the flow starts (`processType: "config"`)
These errors are produced while loading the configuration or running the camera pre-checks, before any UI is shown. They always reject with `statusCode: 3`.
| `reason.name` | `description` | Cause |
|---|---|---|
| `NOT_SUPPORTED` | Dispositivo no soportado | The browser does not provide `mediaDevices.getUserMedia`. |
| `CAMERA_NOT_FOUND` | Cámara no encontrada | No video input device is available (or devices cannot be enumerated). The check does **not** prompt for camera permission. |
| `INVALID_CREDENTIALS` | Credenciales inválidas | The configuration request returned an HTTP error (invalid `apiKey`, `productId` or `projectName`). |
| `CONNECTION_ERROR` | Error de conexión | The configuration request could not reach the server. |
| `INTERNAL_ERROR` | Error interno del sistema | Any other unexpected error while loading the configuration. |
```json
{
"processType": "config",
"statusCode": 3,
"detail": {
"status": "failed",
"reason": { "name": "NOT_SUPPORTED", "description": "Dispositivo no soportado" },
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12"
}
}
```
If the liveness service needs to create the transaction and that request fails, the same reasons (`CONNECTION_ERROR`, `INTERNAL_ERROR`, `INVALID_CREDENTIALS`) are returned with `processType: "liveness"`.
**Concurrent calls.** Calling `initLiveness` or `initCardCapture` while another call on the same `AdoSDK` instance is still running is rejected immediately, with the `processType` of the new call, `statusCode: 3` and `reason.name: "SDK_ERROR"` (`description`: "Error interno del SDK"). Disable your start button while a process is running.
## Timeouts
| Process | Behavior |
|---|---|
| Liveness | One global timer (`timeout`) starts when the liveness component is created. On expiry the component is closed and the call rejects with `TIMEOUT`. |
| Card capture | The `timeout` budget applies **per phase**: (1) loading of the capture engine/models and (2) document capture. The loading phase is extended automatically when the connection is slow (up to 3× `timeout`). On expiry the call rejects with `TIMEOUT` and the `side` that was active. |
The minimum effective value is 90 s. If your application needs to cut earlier, implement your own timer in the host page.
## Configuration prefetch
```javascript
const sdk = new AdoSDK(urlBase, projectName, apiKey, productId);
await sdk.prefetch(); // e.g. on page load
// later, the configuration is served from cache (within cacheTTL)
const result = await sdk.initLiveness({ processId });
```
`prefetch()` only loads and caches the configuration. It does not create transactions and does not open the camera. If the request fails it rejects with the same `processType: "config"` errors described above.
## Lifecycle events (`onEvent`)
```javascript
const result = await sdk.initLiveness({
processId,
onEvent: ({ event, origin, timestamp, detail }) => {
if (origin === 'iframe:liveness' && event === 'camera-ready') hideLoader();
}
});
```
| Field | Description |
|---|---|
| `event` | Event name. |
| `origin` | `"sdk"`, `"iframe:liveness"` or `"iframe:card-capture"`. |
| `timestamp` | Epoch milliseconds. |
| `detail` | Extra data, or `null`. For card capture it also includes `side` and `singleFlow`. |
Events emitted by the SDK (`origin: "sdk"`):
| Event | When |
|---|---|
| `sdk:config-start` / `sdk:config-loaded` / `sdk:config-cached` | Configuration requested / loaded / served from cache |
| `sdk:prefetch-start` / `sdk:prefetch-done` | `prefetch()` started / finished |
| `sdk:transaction-created` | The SDK created the liveness transaction |
| `sdk:iframe-created` | The component was added to the page |
| `sdk:not-supported` / `sdk:camera-not-found` | Camera pre-checks failed |
| `sdk:timeout` | Global timeout reached |
| `sdk:phase-timing` | Card capture finished a phase (`modelLoadMs`, `captureMs`, `totalMs`, `outcome`) |
Events forwarded from the components include, among others: liveness `camera-ready`, `processing-started`, `processing-complete`; card capture `camera-ready`, `sdk-ready`, `resources-loading`, `document-detected`, `page-captured`, `flip-requested`, `scan-complete`, `scan-timeout`, `quality-feedback`, `slow-network`. Exceptions thrown inside your `onEvent` callback are ignored and never interrupt the flow.
## Webhooks
Webhooks are delivered to the URL configured for your project, only if the webhook is active for the project/product. Every webhook uses the same envelope:
```json
{
"transactionId": "10743",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12",
"transactionStatus": "REJECTED",
"stages": [
{
"processType": "LIVENESS",
"status": "REJECTED",
"reason": {
"name": "SPOOFING",
"description": "A potential spoofing attempt was detected during the liveness process.",
"code": "LV-201"
},
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"keyProcessLiveness": "69c265aeca9d4863b61b1fc6a8099f52",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12"
}
]
}
```
`transactionId` identifies the webhook event; use `processId` to correlate it with the SDK response. Values of `transactionStatus` and `status` are upper case (`APPROVED`, `REJECTED`, `FAILED`). Note that `reason.description` in a webhook comes from the webhook catalog and can differ from the text returned by the SDK; match on `reason.name` / `reason.code`.
**1. Liveness result events (generated by the liveness service).** Sent when a liveness transaction is resolved: `NOT_ALERT` (`APPROVED`) and `SPOOFING`, `CLOSED_EYES`, `PAD`, `NOT_ALIVE` (`REJECTED`).
| `reason.name` | `reason.code` | `reason.description` |
|---|---|---|
| `NOT_ALERT` | `LV-100` | Liveness approved with no alerts detected. |
| `SPOOFING` | `LV-201` | A potential spoofing attempt was detected during the liveness process. |
| `CLOSED_EYES` | `LV-202` | The subject's eyes were detected as closed during the capture. |
| `PAD` | `LV-203` | A potential presentation attack was detected (PAD confidence triggered). |
| `NOT_ALIVE` | `LV-204` | The liveness check indicates the subject is not alive. |
**2. SDK events (generated by the SDK).** Sent by the SDK when a process ends in cancellation, timeout or error, and when a card-capture side is captured successfully.
| Process | `reason.name` values |
|---|---|
| Liveness | `PERMISSION_DENIED` (`LV-300`), `USER_CANCELED` (`LV-305`), `TIMEOUT` (`LV-306`), `VALIDATION_FAILED` (`LV-205`), `INTERNAL_ERROR` (`LV-307`), `SDK_ERROR` (`LV-304`) |
| Card capture | `SUCCESS` (`CC-100`), `WRONG_SIDE` (`CC-200`), `PERMISSION_DENIED` (`CC-300`), `CAMERA_NOT_FOUND` (`CC-301`), `CAMERA_IN_USE` (`CC-302`), `NOT_SUPPORTED` (`CC-303`), `DOCUMENT_NOT_DETECTED` (`CC-304`), `NETWORK_ERROR` (`CC-305`), `SDK_ERROR` (`CC-306`), `USER_CANCELED` (`CC-307`), `FOCUS_LOST` (`CC-308`), `TIMEOUT` (`CC-309`), `INTERNAL_ERROR` (`CC-310`) |
Card-capture events carry the side and the status code instead of the liveness fields:
```json
{
"transactionId": "10744",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12",
"transactionStatus": "FAILED",
"stages": [
{
"processType": "CARDCAPTURE",
"side": "FRONT",
"statusCode": 3,
"detail": {
"status": "FAILED",
"reason": { "code": "CC-307", "name": "USER_CANCELED", "description": "The user canceled the process before completion." },
"uidDevice": "1da62007d8974a9fa71e18cba76f6fc1",
"processId": "3f2b9c1e-7a4d-4f2a-9c0c-6c4c2e9b8d12"
}
}
]
}
```
## Migrating from 3.1.0.0
| Behavior | 3.1.0.0 | 3.2.0.0 | Action |
|---|---|---|---|
| Response structure | `processType` / `statusCode` / `detail` | Same | None |
| `reason.description` language | English | Mostly Spanish (`NOT_ALERT` and `PAD` stay in English); card capture may carry the component's own text | Branch on `reason.name` only |
| Network failure loading the configuration | `NETWORK_ERROR` | `CONNECTION_ERROR` | Handle both names |
| `CLOSED_EYES`, `NOT_ALIVE` | Returned as `UNKNOWN` (`statusCode 2`) | Returned with their own name | Handle the new names |
| Liveness result without `reason` | Rejected (`UNKNOWN`, `statusCode 2`) | Approved (`NOT_ALERT`, `statusCode 1`) | Review if you relied on the old behavior |
| Timeout | None: the promise could stay pending forever | `TIMEOUT` after `timeout` (min 90 s) | Handle `TIMEOUT` |
| Camera pre-checks | None | `NOT_SUPPORTED` / `CAMERA_NOT_FOUND` (`processType: "config"`) | Handle them |
| Concurrent calls | Allowed (two components at once) | Second call rejected with `SDK_ERROR` | Disable the start button while running |
| Card capture `SCAN_TIMEOUT` | `INTERNAL_ERROR` | `TIMEOUT` | Handle `TIMEOUT` |
| `side` on card capture failures | May be missing | Always the active side | None |
| `prefetch()`, `onEvent` | Not available | Available (optional) | Optional |
To migrate, change the script URL to `3.2.0.0`; no code changes are required to keep working, but review the table above.