npm.io
8.0.1 • Published yesterday

@datachecker/faceverify

Licence
SEE LICENSE IN LICENSE.md
Version
8.0.1
Deps
1
Size
18.5 MB
Vulns
0
Weekly
0

FaceVerify

Upgrading from older versions? See the Migration Guides for breaking changes.

This project contains Datachecker's FaceVerify tool, that captures images of faces to be used in liveness detection. The tool only takes a capture once the trigger mechanism is fired.

To perform liveness detection, two slightly different images of the same person are required. For example, when a person moves his/her head slightly this will generate a different image. Therefore, the tool checks difference in movement between frames.

The tool features user challenge-response, namely head pose estimation, in order to prevent video injection attacks.

The tool will be run in the browser and is therefore written in JavaScript.

Trigger mechanism

The tool performs the following checks:

  • Is the environment not too dark (under exposure)?
  • Is there a face?
  • Is the face occluded?
  • Is the detected face not too far?
  • Is the detected face not too close?
  • Is the face centered?
  • Is the image sharp?
  • Is there movement?

The movement check will only be used for the second picture. Since the first picture has no other picture to compare to.

Prerequisites

Please visit Datachecker API documentation.

Compatibility

The SDK requires a browser that supports at least ECMAScript 12 (ES12). It is highly recommended to use the latest version of your preferred browser to ensure compatibility and access to the latest features and security updates.

Using the latest browser versions will ensure that all modern JavaScript features required by the SDK are supported.

Supported browsers and devices

The SDK runs the face landmark model in a Web Worker (preferred) and falls back to the main thread when the worker path is unavailable. The decision is made at runtime by a WebGL2 capability probe, so support is decided by what the device can actually do — not by parsing the User-Agent.

Platform Full support (worker, ~30 FPS) Degraded mode (main thread, throttled) Refused
Android Chrome Last 2 major versions Older devices that pass the probe No WebGL2 in worker or main
Desktop Chrome / Edge Last 2 major versions
Desktop Firefox Last 2 ESR + current
iOS Safari (and all iOS browsers — they all use WebKit) iOS 17.0 + iOS 16 (CPU delegate, ~10 FPS) Device fails the runtime probe
macOS Safari 17.0 + 16.x Device fails the runtime probe

Degraded mode runs FaceLandmarker.detectForVideo() synchronously on the main thread. The flow still completes but the camera preview is less smooth — detectForVideo blocks the UI thread between frames. This is the only path that works on iOS 16 (iPhone 8 / 8 Plus / X), where Safari does not support WebGL2 inside Web Workers (added in Safari 17.0).

Refused devices receive device_error:7001 (DEVICE_UNSUPPORTED) on the onError callback before the detection loop starts — see Error Codes. Integrators should map this to a clear "device not supported" message rather than offering a retry.

Steps

  1. Request OAUTH Token
  2. Put OAuth in header
  3. SDK configuration (add SDK token)
  4. Run SDK

OAuth Token

Datachecker uses OAuth authorization. In order to request the SDK token you will need to provide a valid OAuth token in the header.

Example header:

header = {'Authorization': `Bearer ${response.accessToken}`}

This OAuth token can be retrieved with the Datachecker OAuth Token API. The scope "productapi.sdk.read" needs to be present to make use of the SDK token. If this scope is missing you will not be able to retrieve an SDK token.

FaceVerify also requires the other following scopes to send and receive results: "productapi.faceverify.write", "productapi.poll.read", "productapi.result.read"

Example OAuth:

fetch(<BASE_ENDPOINT>+"/oauth/token", {
    method: 'POST',
    body: JSON.stringify({
        "clientId": <CLIENTID>,
        "clientSecret": <CLIENTSECRET>,
        "scopes": [
            "productapi.sdk.read",
            "productapi.faceverify.write",
            "productapi.poll.read",
            "productapi.result.read",
        ]
    })
})
.then(response => response.json())

Note: Contact Datachecker for client_id and client_secret.

SDK Token

The SDK is locked. In order to use the SDK in production a token is required. The application can only be started with a valid token. This token is a base64 string. The token can be generated by calling the Datachecker SDK Token API.

Example:

fetch(<BASE_ENDPOINT>+"/sdk/token?number_of_challenges=2&customer_reference=<CUSTOMER>&validateWatermark=true&services=FACE_VERIFY", {
    method: 'GET',
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Authorization': `Bearer <ACCESSTOKEN>`
    }
})
.then(response => response.json())

Configuration

To run this tool, you will need initialise with the following variables.

ATTRIBUTE FORMAT DEFAULT VALUE EXAMPLE NOTES
ASSETS_FOLDER string "" "../" optional
Specifies location of locally hosted assets folder. (see Asset Fetching Configuration)
ASSETS_MODE string "CDN" "LOCAL" optional
Specifies mode of asset fetching, either through CDN or locally hosted assets. (see Asset Fetching Configuration)
BACKGROUND_COLOR string (Hex color code) "#1d3461" "#1d3461" optional
Specifies the background color using a hex color code.
CONTAINER_ID string "FV_mount" required
div id to mount tool on.
DEBUG bool false false optional
When debug is true more detailed logs will be visible.
DESKTOP_MODE bool false false optional
Enables all cameras for testing/development purposes. FOR TESTING ONLY - DO NOT USE IN PRODUCTION. This mode bypasses camera filtering to allow testing on desktop devices, including virtual cameras. Production environments should always use false to ensure only front-facing cameras (user) are available, preventing accidental use of back-facing cameras on mobile devices.
LANGUAGE string "nl" "nl" required
Notifications in specific language.
onComplete javascript function function(data) {console.log(data)} required
Callback function on complete.
onError javascript function function(error) {console.log(error)} function(error) {console.log(error)} required
Callback function on error.
onUserExit javascript function function(error) {console.log(error)} function(error) {window.history.back()} required
Callback function on user exit.
TOKEN string see SDK Token required
Datachecker SDK token.

Asset fetching Configuration

FaceVerify requires fetching assets, which can be done either through a CDN or by hosting them locally. Configure this in the tool settings as follows:

CDN Configuration
// configuration
{
    ASSETS_MODE: "CDN",
    // other configurations
}
Locally Hosting Configuration

To host assets locally, first copy them to your desired location:

cp -r dist/assets/ path/to/hosted/assets/

Then, configure the tool to use these local assets:

// configuration
{
    ASSETS_MODE: "LOCAL",
    ASSETS_FOLDER: "path/to/hosted/assets/",
    // other configurations
}

For comprehensive integration examples, please refer to our Integration Examples.

Version Control

To ensure compatibility:

  • Separate Asset Versioning: The assets directory contains a version file, separate from the main file's version.
  • Compatibility Check: The main file will perform a version check and throw an error if the versions are incompatible.

Content Security Policy (CSP)

FaceVerify is designed to work with Content Security Policy (CSP) enabled. The SDK requires specific CSP directives to load assets (e.g., scripts, models, images) and execute WebAssembly for face detection.

Required CSP Directives

Ensure your CSP policy includes the following directives. Adjust domains based on your environment (e.g., use https://developer.datachecker.nl for Datachecker production api).

  • default-src 'self';
  • script-src 'self' https://cdn.jsdelivr.net 'wasm-unsafe-eval' 'unsafe-inline' blob:;
  • style-src 'self' 'unsafe-inline';
  • connect-src 'self' https://developer.datachecker.nl https://cdn.jsdelivr.net data:;
  • img-src 'self' data: blob: https://cdn.jsdelivr.net;
  • worker-src 'self' blob:;
  • object-src 'self' blob:;
  • frame-src 'self' blob:;
  • base-uri 'none';

Handling callbacks

Within the application, you can take advantage of three callback functions to enhance the user experience and manage the flow of your process.

Note: When integrating the application into Native Apps using web views, it's essential to adapt and utilize these callback functions according to the conventions and requirements of the native platforms (e.g., iOS, Android). Native app development environments may have specific ways of handling JavaScript callbacks, and you should ensure seamless communication between the web view and the native code.

Example Web (JS):

let FV = new FaceVerify();
FV.init({
    CONTAINER_ID: 'FV_mount',
    LANGUAGE: 'en',
    TOKEN: '<SDK_TOKEN>',
    onComplete: function(data) {
        console.log(data);
    },
    onError: function(error) {
        console.error(error.code, error.stack)
    },
    onUserExit: function(error) {
        console.log(error);
        window.history.back()
    }
});
ATTRIBUTE FORMAT DEFAULT VALUE EXAMPLE NOTES
onComplete javascript function function(data) {console.log(data)} required
Callback that fires when all interactive tasks in the workflow have been completed.
onError javascript function function(error) {console.log(error.code)} function(error) {console.log(error.code)} required
Callback that fires when an error occurs. Receives { code, stack }. See Error Codes.
onUserExit javascript function function(error) {console.log(error)} function(error) {window.history.back()} required
Callback that fires when the user exits the flow without completing it.
onComplete

This callback function will be called once all the tasks within the workflow succesfully have been completed. This callback function is required. The data parameter within the function represents the output of the completed process. You can customize this function to handle and display the data as needed.

Example Web (JS):

Within the example below we are logging the output (data) to console.

let FV = new FaceVerify();
FV.init({
    ...,
    onComplete: function(data) {
        console.log(data);
        // FV.stop() is called automatically - no need to call it here
    }
});
onError

This callback fires when an error occurs during the SDK lifecycle. This callback function is required. The error parameter is an object with two properties:

  • code — A structured error code (e.g., capture_error:4004). Use the category prefix to determine the appropriate UI response. See Error Codes for the full list of categories.
  • stack — A stack trace string. Include this when reporting issues to support for faster diagnosis.

Example Web (JS):

let FV = new FaceVerify();
FV.init({
    ...,
    onError: function(error) {
        console.error(error.code, error.stack);

        if (error.code.startsWith('capture_error')) {
            // Camera issue — show retry UI or prompt for camera permission
        } else if (error.code.startsWith('init_error')) {
            // Initialization failed — prompt user to refresh the page
        } else if (error.code.startsWith('detect_error')) {
            // Face detection failed — prompt user to try again
        }
    }
});
onUserExit

This callback can be used to implement actions like returning users to the previous page or prompting them for confirmation before exiting to ensure they don't lose any unsaved data or work. This callback function is required. The error parameter within the function contains information about the specific error encountered, allowing you to log or display error messages for debugging or user guidance. The error that is thrown is "exit".

Example Web (JS):

Within the example below we are logging the output (error) to console. Finally, we move back one page in the session history with window.history.back().

let FV = new FaceVerify();
FV.init({
    ...,
    onUserExit: function(error) {
        console.log(error);
        window.history.back()
    }
});

Usage/Examples

The tool first needs to be initialised to load all the models. Once its initialised, it can be started with the function FV.start();

let FV = new FaceVerify();
FV.init({
    CONTAINER_ID: ...,
    LANGUAGE: ...,
    TOKEN: ...,
    onComplete: ...,
    onError: ...,
    onUserExit: ...,
}).then(() => {
    FV.start();
});
Cleanup and Removal

The SDK automatically cleans up resources (stops the camera and removes UI elements) when the onComplete, onError, or onUserExit callbacks are triggered.

If you need to completely remove the SDK instance and its container from the DOM (e.g., when unmounting a component or navigating away), use the remove() method:

FV.remove();

This will stop all SDK processes and remove the entire container from the page. This is particularly useful in Single Page Applications (SPAs) or when you need to reinitialize the SDK later.

Example below:

let FV = new FaceVerify();
FV.init({
    CONTAINER_ID: 'FV_mount',
    LANGUAGE: 'nl',
    TOKEN: '<SDK_TOKEN>',
    onComplete: function(data) {
        console.log(data);
    },
    onError: function(error) {
        console.error(error.code, error.stack)
    },
    onUserExit: function(error) {
        console.log(error);
        window.history.back();
    },
});

Importing SDK

Import the SDK with one of the three methods: Script tag, ES6 or CommonJS.

Script Tag

Easily add FaceVerify to your HTML files using the Script Tag method.

<!-- Add FaceVerify directly in your HTML -->
<script src="dist/faceverify.obf.js"></script>
NPM

For projects using NPM and a module bundler like Webpack or Rollup, you can import FaceVerify as an ES6 module or with CommonJS require syntax.

// Import FaceVerify in your JavaScript file

// ES6 style import
import FaceVerify from '@datachecker/faceverify';

// CommonJS style require
let FaceVerify = require('@datachecker/faceverify')

Demo

<!DOCTYPE html>
<html>
<head>
<title>FaceVerify</title>
</head>

<body>
    <div id="FV_mount" style="height:100vh">
    </div>
</body>

<script src="faceverify.obf.js" type="text/javascript"></script>
<script>
    let FV = new FaceVerify();
    FV.init({
        CONTAINER_ID: 'FV_mount',
        LANGUAGE: 'en',
        TOKEN: '<SDK_TOKEN>',
        onComplete: function(data) {
            console.log(data)
        },
        onError: function(error) {
            console.log(error)
        },
        onUserExit: function(error) {
            console.log(error);
            window.history.back();
        },
    });    
</script>

</html>

Languages

Notifications can be loaded in two ways: from a language file or from a JSON object.

Loading from a file

Language files are located in assets/language/. The currently supported languages are:

  • Bulgarian (bg)
  • Czech (cz)
  • German (de)
  • English (en)
  • Spanish (es)
  • French (fr)
  • Hungarian (hu)
  • Italian (it)
  • Lithuanian (lt)
  • Dutch (nl)
  • Papiamento (pa)
  • Polish (pl)
  • Portuguese (pt)
  • Romanian (ro)
  • Slovak (sk)
  • Ukrainian (uk)

Additional languages can be added if needed.

You can load a language in the configuration by setting the LANGUAGE option:

let FV = new FaceVerify();
FV.init({
    LANGUAGE: 'en',
    ...

To create support for a new language, a js file needs to be created with specific keys. The keys can be derived from the current language js files (assets/language/en.js).

Example:

var LANGUAGE = {
    "start_prompt": "Tap to start",
    "no_face": "No face detected",
    "nod_head": "Please nod your head",
    "face_thresh": "Face covered",
    "face_far": "Move closer",
    "face_close": "Move back",
    "exp_dark": "Image is too dark",
    "blur": "Hold still",
    "capture_error": "Camera access is required",
    "challenge_0": "Center your face",
    "detect_error": "Face detection failed. Please try again.",
    "device_error": "This device is not supported.",
    "init_error": "Initialization failed. Please refresh the page.",
    "model_error": "Failed to load required resources. Please check your connection.",
    "runtime_error": "Something went wrong. Please try again.",
    "opencv_error": "A required component failed to load. Please refresh the page.",
    "settings_error": "Configuration error. Please contact support.",
    "token_error": "Authorization failed. Please try again later.",
    "challenge_out": "Face too far",
    "challenge_1": "Look up",
    "challenge_2": "Look right",
    "challenge_3": "Look down",
    "challenge_4": "Look left",
    "tutorial": "Follow the instructions",
    "continue": "Continue"
}
Object (json)

Notifications can also be loaded as a json object like the following:

let FV = new FaceVerify();
FV.init({
    LANGUAGE: JSON.stringify(
        {
            "start_prompt": "Tap to start",
            "no_face": "No face detected",
            "nod_head": "Please nod your head",
            "face_thresh": "Face covered",
            "face_far": "Move closer",
            "face_close": "Move back",
            "exp_dark": "Image is too dark",
            "blur": "Hold still",
            "capture_error": "Camera access is required",
            "challenge_0": "Center your face",
            "detect_error": "Face detection failed. Please try again.",
            "device_error": "This device is not supported.",
            "init_error": "Initialization failed. Please refresh the page.",
            "model_error": "Failed to load required resources. Please check your connection.",
            "runtime_error": "Something went wrong. Please try again.",
            "opencv_error": "A required component failed to load. Please refresh the page.",
            "settings_error": "Configuration error. Please contact support.",
            "token_error": "Authorization failed. Please try again later.",
            "challenge_out": "Face too far",
            "challenge_1": "Look up",
            "challenge_2": "Look right",
            "challenge_3": "Look down",
            "challenge_4": "Look left",
            "tutorial": "Follow the instructions",
            "continue": "Continue"
        }
    ),
    ...

Error Codes

The onError callback receives an object { code, stack }. The code follows the format category:NNNN (e.g., capture_error:4004). Use the category prefix to determine the type of error and the appropriate response.

Category Description Recommended Action
capture_error Camera failure Show camera retry UI or prompt for permission
detect_error Face detection failed after repeated attempts Prompt user to try again or refresh
device_error Device cannot run the face landmarker (no WebGL2 in worker or main thread). Surfaced before start. Show "device not supported" message — do not offer a retry; nothing will change
init_error Initialization failed Prompt user to refresh the page
model_error ML model failed to load Check network connection, retry initialization
opencv_error Required component failed to load Prompt user to refresh or try a different browser
runtime_error Uncategorised failure — no more specific category applies (catch-all of last resort) Show generic retry UI
settings_error Invalid configuration or version mismatch Verify SDK configuration and assets
token_error Token missing, invalid, or not permitted Verify token credentials

Note that runtime_error does not mean "an error that happened at runtime" — most categories above can fire mid-session. It is the catch-all of last resort, reported only when the SDK cannot attribute a failure to any of the specific categories. Specific runtime failures keep their specific category (e.g. repeated detection failures are detect_error, camera loss is capture_error).

When reporting issues to support, include both error.code and error.stack — the numeric identifier in the code and the stack trace allow for precise diagnosis.

The user-facing alert message shown in the SDK overlay is determined by the category prefix, which maps to a key in the Languages dictionary (e.g., capture_error maps to the capture_error language key). If a custom language file does not include a key for a new category (e.g., init_error), the SDK falls back to its built-in English default.

Models

The tool uses a collection of neural networks located in the assets/ directory.

Asset Configuration: Models are part of the assets and can be fetched via CDN (default) or hosted locally. See Asset Fetching Configuration for details.

Challenges

User challenges are implemented to prevent video injection attacks. These challenges are randomly chosen and thereby, processes are different from one another. The challenges consist of head pose estimation. The performed head poses will be compared with the challenges and that result will be returned as bool in output. (see Output)

There are four poses that will be detected:

  • up
  • right
  • down
  • left

Challenges are managed exclusively through the SDK token. Specify the number of challenges when requesting your SDK token:

fetch(BASE_ENDPOINT + "/sdk/token?number_of_challenges=2&customer_reference=<CUSTOMER>&validateWatermark=true&services=FACE_VERIFY", {
    method: 'GET',
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'Authorization': `Bearer <ACCESSTOKEN>`
    }
})
.then(response => response.json())

The challenges are embedded in the TOKEN and are not directly configurable in the SDK initialization.

Output

The SDK will output in the following structure:

{   
    "images": [
        {
            "data":"<BASE64_IMG>", 
            "type":"LIVE"
        },
        "..."
    ],
    "meta": [
        {
            "x":"", 
            "y":"", 
            "width":"", 
            "height":"", 
            "device":""
        },
        "..."
    ],
    "token": "<SDK_TOKEN>",
    "transactionId": "<TRANSACTION_ID>",
    "valid_challenges": "true|false"
}

Each data field is a base64-encoded image. As of v8, the first LIVE image (the identity image) is JPEG and the subsequent images are PNG; detect the content type from the data rather than assuming a format. Forward the output to the DataChecker API unmodified — re-encoding or editing the images will cause the request to be rejected.

Each meta entry describes the corresponding capture: x/y/width/height locate the face region and device names the camera used.

Example:

{   
    "images": [
        {
            "data":"/9j/4AAQSkZJRgABAQAAAQABAAD/...", 
            "type":"LIVE"
        }, 
        {
            "data":"/9j/4AAQSkZJRgABAQAAAQABAAD/...", 
            "type":"LIVE"
        }, 
        {
            "data":"/9j/4AAQSkZJRgABAQAAAQABAAD/...", 
            "type":"LIVE"
        }
    ],
    "meta": [
        {
            "x": 33, 
            "y": 182, 
            "width": 265, 
            "height": 354, 
            "device": "Front camera"
        }, 
        {
            "x": 33, 
            "y": 182, 
            "width": 265, 
            "height": 354, 
            "device": "Front camera"
        }, 
        {
            "x": 33, 
            "y": 182, 
            "width": 265, 
            "height": 354, 
            "device": "Front camera"
        }
    ],
    "token": "<SDK_TOKEN>",
    "transactionId": "<TRANSACTION_ID>",
    "valid_challenges": true
}
FaceVerify API call

If you want to send the images to the Datachecker FaceVerify API you must add a comparison image. This comparison image can either be a portrait picture from an identity card or a selfie. To add this image, you need to use type: "COMPARE".

Example JS:

let faceverify_output = {   
    "images": [
        {
            "data":"/9j/4AAQSkZJRgABAQAAAQABAAD/...", 
            "type":"LIVE"
        }, 
        {
            "data":"/9j/4AAQSkZJRgABAQAAAQABAAD/...", 
            "type":"LIVE"
        }, 
        {
            "data":"/9j/4AAQSkZJRgABAQAAAQABAAD/...", 
            "type":"LIVE"
        }
    ],
    "meta": [
        {
            "x": 33, 
            "y": 182, 
            "width": 265, 
            "height": 354, 
            "device": "Front camera"
        }, 
        {
            "x": 33, 
            "y": 182, 
            "width": 265, 
            "height": 354, 
            "device": "Front camera"
        }, 
        {
            "x": 33, 
            "y": 182, 
            "width": 265, 
            "height": 354, 
            "device": "Front camera"
        }
    ],
    "token": "<SDK_TOKEN>",
    "transactionId": "<TRANSACTION_ID>",
    "valid_challenges": true
}
let images = faceverify_output.images
let portrait_image = {"data":"/9j/4AAQSkZJRgABAQAAAQABAAD/...", "type":"COMPARE"}

images.unshift(portrait_image)
let data = {"images": images, "transaction_id":faceverify_output.transactionId}


fetch(<BASE_ENDPOINT>+"/faceverify", {
        method: 'POST',
        headers: <HEADERS>,
        body: JSON.stringify(data)
        })
        .then(response => response.json())