Active Liveness
curl --request POST \
--url https://verilock.io/api/v1/sessions/{id}/liveness-challenge \
--header 'Authorization: Bearer <token>'import requests
url = "https://verilock.io/api/v1/sessions/{id}/liveness-challenge"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://verilock.io/api/v1/sessions/{id}/liveness-challenge', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verilock.io/api/v1/sessions/{id}/liveness-challenge",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://verilock.io/api/v1/sessions/{id}/liveness-challenge"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://verilock.io/api/v1/sessions/{id}/liveness-challenge")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://verilock.io/api/v1/sessions/{id}/liveness-challenge")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyVerification Enhancements
Active Liveness
Multi-frame liveness detection with head turn, smile, and blink challenges.
POST
/
sessions
/
{id}
/
liveness-challenge
Active Liveness
curl --request POST \
--url https://verilock.io/api/v1/sessions/{id}/liveness-challenge \
--header 'Authorization: Bearer <token>'import requests
url = "https://verilock.io/api/v1/sessions/{id}/liveness-challenge"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://verilock.io/api/v1/sessions/{id}/liveness-challenge', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://verilock.io/api/v1/sessions/{id}/liveness-challenge",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://verilock.io/api/v1/sessions/{id}/liveness-challenge"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://verilock.io/api/v1/sessions/{id}/liveness-challenge")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://verilock.io/api/v1/sessions/{id}/liveness-challenge")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyActive Liveness adds interactive challenges during the selfie step — requiring the user to turn their head, smile, and blink — to provide stronger anti-spoofing protection than passive liveness alone.
Passive vs Active Liveness
| Feature | Passive Liveness | Active Liveness |
|---|---|---|
| User action | None (single photo) | Head turns, smile, blink |
| Frames analyzed | 1 | 8 — 12 |
| Spoof resistance | Standard | High |
| Detects printed photos | Yes | Yes |
| Detects screen replay | Limited | Yes |
| Detects 3D masks | Limited | Yes |
| User friction | Very low | Low — moderate |
| Best for | Low-risk onboarding | Regulated industries, high-value accounts |
How It Works
1
Challenge prompt
The user is shown a series of on-screen prompts: turn head left, turn head right, smile, and blink. The order is randomized per session.
2
Multi-frame capture
The SDK captures 8-12 frames during the challenge sequence, analyzing pose variation and expression changes.
3
Scoring
Each frame is evaluated for pose angle, expression detection, and texture analysis. The scores are aggregated into a final liveness result.
4
Result
The session receives a liveness score (0-100) and a pass/fail determination based on the configured threshold.
Configuration
Enable active liveness in the Dashboard > Workflows > Liveness Settings, or set it per session via the API:{
"profile": {
"steps": ["document", "selfie"],
"liveness_mode": "active"
}
}
| Field | Type | Default | Description |
|---|---|---|---|
liveness_mode | string | passive | passive or active |
liveness_threshold | integer | 75 | Minimum score to pass (0-100) |
liveness_challenges | array | all | Subset of challenges: head_left, head_right, smile, blink |
Example: Custom Challenges
{
"profile": {
"steps": ["document", "selfie"],
"liveness_mode": "active",
"liveness_threshold": 80,
"liveness_challenges": ["head_left", "head_right", "blink"]
}
}
Scoring Breakdown
The active liveness score is composed of two primary factors:| Factor | Weight | Description |
|---|---|---|
| Pose variation | 50% | Measures actual head movement across frames. Higher variation indicates genuine motion. |
| Expression variation | 50% | Detects expression changes (smile, blink). Ensures the user is responding to prompts. |
Score Thresholds
| Score | Interpretation |
|---|---|
| 90 — 100 | Very high confidence of a live person |
| 75 — 89 | High confidence — default pass threshold |
| 50 — 74 | Moderate confidence — may warrant manual review |
| 0 — 49 | Low confidence — likely spoofed |
API Response
The session detail response includes theliveness_mode field and active liveness data:
{
"id": "ses_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "approved",
"selfie": {
"liveness_mode": "active",
"liveness_score": 92.5,
"liveness_passed": true,
"liveness_threshold": 75,
"challenges_completed": ["head_left", "head_right", "smile", "blink"],
"frames_captured": 10,
"pose_variation_score": 94.0,
"expression_variation_score": 91.0
}
}
| Field | Type | Description |
|---|---|---|
liveness_mode | string | passive or active |
liveness_score | number | Aggregated liveness score (0-100) |
liveness_passed | boolean | Whether the score met the threshold |
liveness_threshold | integer | Threshold used for pass/fail |
challenges_completed | array | Challenges the user completed |
frames_captured | integer | Number of frames analyzed |
pose_variation_score | number | Head movement score (0-100) |
expression_variation_score | number | Expression change score (0-100) |
Active liveness requires the Verilock Web SDK or mobile SDKs. It is not available for API-only document upload flows.
Start with passive liveness for low-risk use cases and upgrade to active liveness for higher-risk scenarios. You can use different liveness modes across different verification workflows.

