🙂 Face Active Liveness Detection
Welcome to Face Active Liveness Detection API, an AI product developed by iApp Technology Co., Ltd. Unlike passive liveness (which analyzes a single image), active liveness asks the user to perform randomized challenges — blink, turn left, turn right, smile — in front of the camera, proving that a live, cooperative person is present. The session is finalized server-side, and the API returns a cryptographically signed verdict that your backend can verify independently, so a modified client can never forge a "passed" result.
Try the SDK (Live Camera)
Run the full challenge flow right here in your browser with the free open-source iApp eKYC Web SDK — it locks onto your face, issues randomized challenges, selects the best selfie frame, and submits it to the finalize API. See more flows on the full SDK live demo page.
Loading live demo…
How It Works
The flow is designed to be driven by our free open-source eKYC SDK (Flutter and Web), which handles the camera, face tracking, and challenge logic on-device:
- Face lock — the SDK finds exactly one frontal face and waits for it to be stable and well-framed.
- Randomized challenges — it draws 3 distinct challenges at random from blink / turn left / turn right / smile and verifies each one on-device using real-time face landmarks (e.g. a blink must be a closed-then-open transition, so a printed photo of closed eyes cannot pass).
- Best-frame selection — across the whole session, the SDK scores every sharp, frontal, eyes-open frame and keeps the best selfie (sharpness × face size).
- Finalize — the SDK submits the best selfie plus the timestamped challenge log to
POST /v3/store/ekyc/face-active-liveness/finalize. - Server re-verification — the server validates the challenge log (allowlisted challenge types, at least 2 challenges, all passed, strictly monotonic timestamps, sane per-challenge durations, fresh session) and independently re-checks the selfie with our iBeta Level 1 certified passive-liveness engine.
- Signed verdict — the server returns a verdict signed with HMAC-SHA256. The verdict embeds the selfie's SHA-256 hash, binding the decision to the exact image bytes. Your backend verifies the signature with the shared secret issued by iApp and trusts only the signed verdict — never the client's own claim.
Quick Start with the SDK
The fastest way to integrate active liveness is the free, Apache-2.0 licensed iApp eKYC SDK — see the SDK Getting Started guide.
Flutter
# pubspec.yaml
dependencies:
iapp_ekyc_sdk:
git:
url: https://github.com/iapp-technology/iapp-ekyc-sdk.git
path: flutter
import 'package:iapp_ekyc_sdk/iapp_ekyc_sdk.dart';
final client = IappEkycClient(apiKey: 'YOUR_API_KEY');
// Face active liveness with signed server verdict
final liveness = await ActiveLivenessView.start(context, client: client);
if (liveness.verdict.passed) { /* proceed with onboarding */ }
Web (JavaScript)
npm install @iapp-technology/ekyc-sdk
import { IappEkyc } from '@iapp-technology/ekyc-sdk';
const ekyc = new IappEkyc({ apiKey: 'YOUR_API_KEY' });
const liveness = await ekyc.startActiveLiveness({
mount: document.getElementById('ekyc-mount'),
});
Getting Started
-
Prerequisites
- An API key from iApp Technology
- The eKYC SDK (recommended) or an equivalent on-device challenge implementation
- Selfie formats: JPEG, JPG, PNG
- Maximum file size: 10MB
-
Quick Start
- Drop-in SDK camera flows for Flutter (Android/iOS) and Web
- Randomized challenge sequence on every session
- Server-signed verdict for tamper-proof integration
- Backed by our iBeta Level 1 certified passive-liveness engine
-
Key Features
- Blink, turn-left, turn-right, and smile challenges
- Anti-cheat: session restarts on face loss, multiple faces, or identity switch
- Best-frame selfie selection (sharpness-scored)
- HMAC-SHA256 signed verdict bound to the selfie's SHA-256 hash
-
Security & Compliance
- GDPR and PDPA compliant
- No image data retention after processing
- Signed verdicts verifiable offline on your backend
Please visit API Key Management page to view your existing API key or request a new one.
Example
Face Active Liveness Finalize Request:
The SDK builds this request for you. If you call the API directly, submit the best selfie frame plus the JSON challenge log recorded on-device:
curl --location 'https://api.iapp.co.th/v3/store/ekyc/face-active-liveness/finalize' \
--header 'apikey: {YOUR API KEY}' \
--form 'file=@"selfie.jpg"' \
--form 'challenges={
"session_id": "b0e7c1a2-4f5d-4e6a-9b8c-7d6e5f4a3b2c",
"sdk": { "name": "iapp-ekyc-sdk-flutter", "version": "0.1.0", "platform": "android" },
"started_at": 1767500000000,
"finished_at": 1767500008000,
"challenges": [
{ "type": "blink", "issued_at": 1767500000123, "completed_at": 1767500001873, "passed": true },
{ "type": "turn_left", "issued_at": 1767500002000, "completed_at": 1767500004100, "passed": true },
{ "type": "smile", "issued_at": 1767500004500, "completed_at": 1767500006900, "passed": true }
]
}'
Face Active Liveness Finalize Response:
{
"verdict": {
"passed": true,
"passive_liveness": { "predict": "REAL", "real_score": 0.9999, "threshold": 0.5 },
"challenge_summary": {
"total": 3,
"passed": 3,
"types": ["blink", "turn_left", "smile"],
"duration_ms": 8000,
"valid": true,
"reasons": []
},
"session_id": "b0e7c1a2-4f5d-4e6a-9b8c-7d6e5f4a3b2c",
"selfie_sha256": "ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12cd34ef56ab12",
"timestamp": "2026-07-04T09:00:00.000Z",
"nonce": "9f3a1c7e2b8d4f60"
},
"signature": "hex(HMAC-SHA256(secret, canonicalJSON(verdict)))",
"signature_alg": "HMAC-SHA256",
"process_time": 0.42
}
Verifying the Signature (Node.js):
Your backend must recompute the HMAC over the canonical JSON of verdict (all object keys sorted recursively, no insignificant whitespace, UTF-8) using the shared secret issued by iApp, and compare in constant time:
const crypto = require('crypto');
const sortKeysDeep = (v) =>
Array.isArray(v) ? v.map(sortKeysDeep)
: v && typeof v === 'object'
? Object.fromEntries(Object.keys(v).sort().map((k) => [k, sortKeysDeep(v[k])]))
: v;
const canonical = (o) => JSON.stringify(sortKeysDeep(o));
const expected = crypto.createHmac('sha256', SECRET).update(canonical(verdict)).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'));
Then check verdict.passed, verdict.timestamp freshness, and — if the selfie is transported separately — that its SHA-256 equals verdict.selfie_sha256.
Features & Capabilities
Core Features
- Randomized on-device challenge sequence (blink, turn left, turn right, smile) on every session — replay-resistant by design.
- Server-side re-verification of the selfie with our iBeta Level 1 certified passive-liveness engine.
- HMAC-SHA256 signed verdict that binds the decision to the exact selfie bytes via
selfie_sha256. - Strict challenge-log validation: allowlisted types, minimum 2 challenges, strictly monotonic timestamps, per-challenge duration 300 ms–30 s, session length ≤ 120 s, freshness within 5 minutes of server time.
- Free open-source client SDKs for Flutter (Android/iOS) and Web with a fully themable UI in English, Thai, and Chinese.
Supported Fields
- Pass/fail liveness verdict with passive-liveness score and threshold.
- Per-session challenge summary (types, counts, duration, validation reasons).
- Optional base64 echo of the verified selfie (
return_image=true). - Compatibility with JPEG, JPG, and PNG selfie images.
API Endpoints
| Endpoint | Method | Description | Cost |
|---|---|---|---|
POST /v3/store/ekyc/face-active-liveness/finalize | POST | Finalizes an active liveness session — validates the challenge log, re-verifies the selfie, and returns a signed verdict | 1 IC per request |
API Reference
Face Active Liveness Endpoints
1. Face Active Liveness Finalize
POST /v3/store/ekyc/face-active-liveness/finalize
Finalizes an active liveness session. Validates the on-device challenge log, re-verifies the best selfie frame with the passive-liveness engine, and returns an HMAC-SHA256 signed verdict.
Calling this endpoint directly requires your own implementation of equivalent on-device challenges (randomized selection, real-time landmark verification, honest wall-clock timestamps). The eKYC SDK is the intended client and handles all of this for you.
Request & Response Format
Headers
| Name | Type | Description |
|---|---|---|
| apikey | String | Your API Key to call this API |
Request Body (multipart/form-data)
| Name | Type | Required | Description |
|---|---|---|---|
| file | File | Yes | Best selfie frame from the session (JPEG/PNG, magic-byte validated server-side, max 10MB) |
| challenges | String | Yes | JSON string of the challenge log — session ID, SDK info, and per-challenge timestamps (see example above) |
| return_image | String | No | Set to "true" to receive the selfie echoed back as base64 in the selfie field (default omitted) |
Parameter in Response
| Name | Type | Description |
|---|---|---|
| verdict | Dictionary | The signed liveness verdict object |
| verdict.passed | Boolean | Overall result — true only if the challenge log is valid AND the selfie passes passive liveness |
| verdict.passive_liveness | Dictionary | Passive-liveness re-check: predict (REAL/SPOOF), real_score, threshold |
| verdict.challenge_summary | Dictionary | Challenge validation: total, passed, types, duration_ms, valid, reasons |
| verdict.session_id | String | Session UUID echoed from the challenge log |
| verdict.selfie_sha256 | String | SHA-256 hash (64 hex chars) of the uploaded selfie — binds the signature to the image bytes |
| verdict.timestamp | String | Server time of the verdict (ISO 8601) |
| verdict.nonce | String | Random nonce making every verdict unique |
| signature | String | Hex HMAC-SHA256 of the canonical JSON of verdict, keyed with your shared secret |
| signature_alg | String | Always HMAC-SHA256 |
| selfie | Dictionary | Only when return_image=true: filename, content_type, size, image_base64 |
| process_time | Float | Server processing time in seconds |
Error Codes
| Code | Error | Description |
|---|---|---|
| 400 | INVALID_CHALLENGE_LOG / INVALID_IMAGE / MISSING_FIELD | Malformed challenge log, invalid image, or missing required field (with reasons array) |
| 401 | Invalid API key | Missing or invalid apikey header (gateway) |
| 402 | Insufficient credit | Top up at Credits (gateway) |
| 413 | File too large | Selfie exceeds the 10MB limit |
| 502 | UPSTREAM_UNAVAILABLE | Liveness engine temporarily unavailable — retry later |
A completed check that returns "passed": false is still an HTTP 200 and is billed 1 IC — the liveness check ran and produced a signed verdict. Error responses (400/401/402/413/502) are never billed.
Pricing
| Operation | Production Path | IC Cost | Unit | On-Premise |
|---|---|---|---|---|
| Face Active Liveness Finalize | /v3/store/ekyc/face-active-liveness/finalize | 1 IC | per 1 request | Contact us |