Skip to main content

Handling Liveness Results

<liveness-check> (and LivenessReact) dispatch standard DOM events. Face matching is a session step, not a widget event — after document capture and liveness both succeed, fetch session results from your backend.

Events

EventWhen it firesevent.detail
yesid:successLiveness passes and submitResults() succeeds{ response, submission }
yesid:failureLiveness, session, or submission failsLivenessError
yesid:captureA challenge capture is producedLivenessCapture
yesid:session-validatedSession accepted by the APISessionValidatedEvent
yesid:session-invalidSession invalid or expiredSessionInvalidEvent
import { YESID_EVENTS } from '@yesid/liveness-sdk';

el.addEventListener(YESID_EVENTS.success, (event) => {
console.log(event.detail.response.status);
});

Success payload

type LivenessSuccessPayload = {
response: {
sessionId: string;
stepType: 'LIVENESS' | 'OCR' | 'FACE_MATCH';
status: 'PENDING' | 'RUNNING' | 'PASSED' | 'FAILED' | 'ERROR' | 'REVIEW' | 'SKIPPED';
completedAt: string;
data?: Record<string, string>;
};
submission: {
captures: string[];
challenges: ('BLINK' | 'TURN_LEFT' | 'TURN_RIGHT' | 'LOOK_UP' | 'LOOK_DOWN' | 'LOOK_CENTER')[];
};
};

submission.captures are the selfie frames collected during the flow. Prefer fetching the canonical selfie from session results if you need it later (steps[].resultData.selfieImage on the LIVENESS step).

el.addEventListener('yesid:success', (event) => {
console.log(event.detail.response.sessionId);
console.log(event.detail.response.status);
console.log(event.detail.submission.captures.length);
});

Failure payload

type LivenessError = {
reason:
| 'CAMERA_PERMISSION_DENIED'
| 'NO_FACE_DETECTED'
| 'MULTIPLE_FACES_DETECTED'
| 'CHALLENGE_TIMEOUT'
| 'INITIALIZATION_FAILED'
| 'FACE_LOST'
| 'SESSION_INVALID'
| 'SESSION_EXPIRED'
| 'SESSION_COMPLETION_FAILED';
message: string;
challenge?: 'BLINK' | 'TURN_LEFT' | 'TURN_RIGHT' | 'LOOK_UP' | 'LOOK_DOWN' | 'LOOK_CENTER';
};

Timeouts and denied camera permission are often recoverable — let the user retry without treating them as a hard failure. SESSION_INVALID / SESSION_EXPIRED should create a new session.

Face match (session results)

The liveness widget does not emit a face-match event. If the session was created with "faceMatch": { "enabled": true }, yes:id runs matching automatically after document capture and liveness both succeed.

Poll your backend (which proxies the secret-key results API) until the FACE_MATCH step appears:

async function fetchFaceMatch(sessionId) {
const response = await fetch(`/session-results/${encodeURIComponent(sessionId)}`);
const data = await response.json();
const steps = data.steps ?? data.data?.steps ?? [];
return steps.find((step) => step.type === 'FACE_MATCH') ?? null;
}

Typical step shape:

{
"type": "FACE_MATCH",
"status": "SUCCEEDED",
"score": 0.91,
"resultData": {
"matched": true,
"similarity": 91
}
}

Interpreting similarity

RangeRecommendation
>= 90High-confidence match
75 – 89Probable match — apply additional context checks
< 75No match

resultData.similarity in session results is a percentage (0–100). Keep polling while status is not terminal (SUCCEEDED, FAILED, ERROR, REQUIRES_REVIEW, SKIPPED).

Capture event

type LivenessCapture = {
timestamp: number;
challenge: 'BLINK' | 'TURN_LEFT' | 'TURN_RIGHT' | 'LOOK_UP' | 'LOOK_DOWN' | 'LOOK_CENTER';
imageBase64: string;
imageBlob: Blob;
width: number;
height: number;
};

Use this if you need per-challenge frames. For verification, rely on yesid:success plus backend session results.

Support

The SDK is tested on current Chrome, Safari, Firefox, and Opera. Camera capture requires a secure context (https or localhost).