Liveness
Is it a real human being looking at the camera? Not a mannequin or a 3D image? The Liveness verification module is created to check whether the biometric information source accessing the camera is a physically present live person.
Liveness detection empowers identity verification systems by enhancing their protection against spoofing attacks, deepfakes, and fraud.
Liveness verification can be performed from any device with a camera. The person only needs to follow the animated instructions. The system records the video of the session and analyzes it with the help of AI algorithms.
Display Liveness
Liveness comes with the configuration allowing you to change the behavior and appearance of some UI elements. For more UI customization, check out UI Customization
Info
At the moment, the configuration is only available for the iOS and Android platforms.
Starting the Liveness process with configuration:
let configuration = LivenessConfiguration { _ in
}
FaceSDK.service.startLiveness(
from: viewController,
animated: true,
configuration: configuration,
onLiveness: { response in
// ... check response.liveness for detection result.
},
completion: nil
)
RFSLivenessConfiguration *configuration = [RFSLivenessConfiguration configurationWithBuilder:^(RFSLivenessConfigurationBuilder * _Nonnull builder) {
}];
[RFSFaceSDK.service startLivenessFrom:viewController
animated:YES
configuration:configuration
onLiveness:^(RFSLivenessResponse * _Nonnull response) {
// ... check response.liveness for detection result.
} completion:nil];
val configuration = LivenessConfiguration.Builder()
.build()
FaceSDK.Instance().startLiveness(this@MainActivity, configuration) { response ->
// ... check response.getLiveness(); for detection result.
}
LivenessConfiguration configuration = new LivenessConfiguration.Builder()
.build();
FaceSDK.Instance().startLiveness(MainActivity.this, configuration, livenessResponse -> {
// ... check livenessResponse.liveness for detection result.
});
FaceSDK.startLiveness(livenessResponse => {
const response = LivenessResponse.fromJson(JSON.parse(livenessResponse));
// ... check response.liveness for detection result.
}, e => { });
FaceSDK.startLiveness().then((livenessResponse) {
var response = LivenessResponse.fromJson(jsonDecode(livenessResponse));
// ... check response.liveness for detection result.
});
FaceSDK.startLiveness(livenessResponse => {
const response = FaceSDK.LivenessResponse.fromJson(JSON.parse(livenessResponse));
// ... check response.liveness for detection result.
}, e => { });
FaceSDK.startLiveness().then(livenessResponse => {
const response = LivenessResponse.fromJson(JSON.parse(livenessResponse));
// ... check response.liveness for detection result.
});
To keep the default configuration, you can omit the configuration
parameter.
To stop the Liveness processing programmatically, there is a call:
FaceSDK.service.stopLivenessProcessing()
[RFSFaceSDK.service stopLivenessProcessing];
FaceSDK.Instance().stopLivenessProcessing(this@MainActivity)
FaceSDK.Instance().stopLivenessProcessing(MainActivity.this);
FaceSDK.stopLivenessProcessing(() => {}, () => {});
FaceSDK.stopLivenessProcessing();
FaceSDK.stopLivenessProcessing();
FaceSDK.stopLivenessProcessing();
Session ID
When starting, you can specify sessionId
which all attempts to read liveness will be bound to. If you don't specify it, the application will generate it automatically.
The calling application returns sessionId
and transactionId
. One session may include many transactions, and from a transaction ID it is clear which session it belongs to.
You can get the metadata (device model, screen data, frame size, app ID and version, OS version, platform, SDK version, etc.), person's selfie, and video of the liveness detection session. They will be saved on the backend at the following path:
faceapi-session/year={year}/month={month}/day={day}/hour={hour}/{sessionId}/transactionId
.
To access liveness transaction artifacts, use the GET /api/v2/liveness?transactionId={transactionId}
OpenAPI request: See OpenAPI documentation.
Setting up the session ID:
let configuration = LivenessConfiguration {
$0.sessionId = UUID().uuidString
}
FaceSDK.service.startLiveness(
from: viewController,
animated: true,
configuration: configuration,
onLiveness: { response in
},
completion: nil
)
RFSLivenessConfiguration *configuration = [RFSLivenessConfiguration configurationWithBuilder:^(RFSLivenessConfigurationBuilder * _Nonnull builder) {
builder.sessionId = [[NSUUID UUID] UUIDString];
}];
[RFSFaceSDK.service startLivenessFrom:viewController
animated:YES
configuration:configuration
onLiveness:^(RFSLivenessResponse * _Nonnull response) {
} completion:nil];
val configuration = LivenessConfiguration.Builder()
.setSessionId(UUID.randomUUID().toString())
.build()
FaceSDK.Instance().startLiveness(context, configuration) { livenessResponse: LivenessResponse? -> }
LivenessConfiguration configuration = new LivenessConfiguration.Builder()
.setSessionId(UUID.randomUUID().toString())
.build();
FaceSDK.Instance().startLiveness(context, configuration, livenessResponse -> {});
To see the status of the session video upload to the service, see Session Video Upload Status
Response
Status
LivenessResponse
contains the response.json file, where you can find livenessStatus
with the result of processing: 0
if the person's liveness is confirmed, 1
if not. Also, the errors may be returned.
To determine the result of the detection, check for the liveness property:
if case response.liveness = .passed {
// continue with successful path.
}
if (response.liveness == RFSLivenessStatusPassed) {
// continue with successful path.
}
Log.d("liveness", "Session id: " + response.sessionId)
Log.d("liveness", "Transaction id: " + response.transactionId)
Log.d("liveness", "Session id: " + response.getSessionId());
Log.d("liveness", "Transaction id: " + response.getTransactionId());
if (response.liveness == LivenessStatus.PASSED) {
// continue with successful path.
}
if (response?.liveness == LivenessStatus.PASSED) {
// continue with successful path.
}
if (response.liveness == FaceSDK.Enum.LivenessStatus.PASSED) {
// continue with successful path.
}
if (response.liveness == LivenessStatus.PASSED) {
// continue with successful path.
}
Start and Done steps
The liveness detection session consists of three steps:
- Start — an animated hint and the Go button. When one clicks on Go, the liveness session starts;
- Session — the liveness detection session;
- Done — the result, a picture with the Done icon.
The Start and Done steps can be turned off.
Disabling Start
let configuration = LivenessConfiguration {
$0.stepSkippingMask = [.onboarding]
}
FaceSDK.service.startLiveness(
from: viewController,
animated: true,
configuration: configuration,
onLiveness: { response in
},
completion: nil
)
RFSLivenessConfiguration *configuration = [RFSLivenessConfiguration configurationWithBuilder:^(RFSLivenessConfigurationBuilder * _Nonnull builder) {
builder.stepSkippingMask = RFSLivenessStepSkipOnboarding;
}];
[RFSFaceSDK.service startLivenessFrom:viewController
animated:YES
configuration:configuration
onLiveness:^(RFSLivenessResponse * _Nonnull response) {
} completion:nil];
val configuration = LivenessConfiguration.Builder()
.setSkipStep(LivenessSkipStep.START_STEP)
.build()
FaceSDK.Instance().startLiveness(
context, configuration
) { livenessResponse: LivenessResponse? -> }
LivenessConfiguration configuration = new LivenessConfiguration.Builder()
.setSkipStep(LivenessSkipStep.START_STEP)
.build();
FaceSDK.Instance().startLiveness(context, configuration, livenessResponse -> {});
Disabling Done
let configuration = LivenessConfiguration {
$0.stepSkippingMask = [.success]
}
FaceSDK.service.startLiveness(
from: viewController,
animated: true,
configuration: configuration,
onLiveness: { response in
},
completion: nil
)
RFSLivenessConfiguration *configuration = [RFSLivenessConfiguration configurationWithBuilder:^(RFSLivenessConfigurationBuilder * _Nonnull builder) {
builder.stepSkippingMask = RFSLivenessStepSkipSuccess;
}];
[RFSFaceSDK.service startLivenessFrom:viewController
animated:YES
configuration:configuration
onLiveness:^(RFSLivenessResponse * _Nonnull response) {
} completion:nil];
val configuration = LivenessConfiguration.Builder()
.setSkipStep(LivenessSkipStep.DONE_STEP)
.build()
FaceSDK.Instance().startLiveness(
context, configuration) { livenessResponse: LivenessResponse? -> }
LivenessConfiguration configuration = new LivenessConfiguration.Builder()
.setSkipStep(LivenessSkipStep.DONE_STEP)
.build();
FaceSDK.Instance().startLiveness(context, configuration, livenessResponse -> {});
Disabling both Start and Done
let configuration = LivenessConfiguration {
$0.stepSkippingMask = [.onboarding, .success]
}
FaceSDK.service.startLiveness(
from: viewController,
animated: true,
configuration: configuration,
onLiveness: { response in
},
completion: nil
)
RFSLivenessConfiguration *configuration = [RFSLivenessConfiguration configurationWithBuilder:^(RFSLivenessConfigurationBuilder * _Nonnull builder) {
builder.stepSkippingMask = RFSLivenessStepSkipOnboarding | RFSLivenessStepSkipSuccess;
}];
[RFSFaceSDK.service startLivenessFrom:viewController
animated:YES
configuration:configuration
onLiveness:^(RFSLivenessResponse * _Nonnull response) {
} completion:nil];
val configuration = LivenessConfiguration.Builder()
.setSkipStep(LivenessSkipStep.START_STEP, LivenessSkipStep.DONE_STEP)
.build()
FaceSDK.Instance().startLiveness(
context, configuration) { livenessResponse: LivenessResponse? -> }
LivenessConfiguration configuration = new LivenessConfiguration.Builder()
.setSkipStep(LivenessSkipStep.START_STEP, LivenessSkipStep.DONE_STEP)
.build();
FaceSDK.Instance().startLiveness(context, configuration, livenessResponse -> {});
Error Handling
The errors and exceptions give you an insight into what happened to the Liveness processing from a user canceling the operation to a missing license on a web service.
Here is an example of how you can handle a failed LivenessResponse
:
if let error = response.error {
// There is an error. Lets see what type is it.
switch error {
case LivenessError.cancelled:
Log.d("User cancelled the processing.")
case LivenessError.noLicense:
Log.d("Web Service is missing a valid License.")
case LivenessError.processingAttemptsEnded:
Log.d("Reached the number of possible attempts. See `RFSLivenessConfiguration.attemptsCount` for more information.")
case LivenessError.processingFailed:
Log.d("Bad input data.")
case LivenessError.processingTimeout:
Log.d("Processing finished by timeout.")
case LivenessError.apiCallFailed:
Log.d("Web Service API call failed due to networking error or backend internal error.")
default:
break
}
}
if (response.error) {
// There is an error. Lets see what type is it.
switch ((RFSLivenessError)response.error.code) {
case RFSLivenessErrorCancelled:
NSLog(@"User cancelled the processing.");
break;
case RFSLivenessErrorNoLicense:
NSLog(@"Web Service is missing a valid License.");
break;
case RFSLivenessErrorProcessingAttemptsEnded:
NSLog(@"Reached the number of possible attempts. See `RFSLivenessConfiguration.attemptsCount` for more information.");
break;
case RFSLivenessErrorProcessingFailed:
NSLog(@"Bad input data.");
break;
case RFSLivenessErrorProcessingTimeout:
NSLog(@"Processing finished by timeout.");
break;
case RFSLivenessErrorAPICallFailed:
NSLog(@"Web Service API call failed due to networking error or backend internal error.");
break;
}
}
val exception = response.exception
if (exception != null) {
when (exception.errorCode) {
LivenessErrorCode.CANCELLED ->
Log.d("User cancelled the processing.")
LivenessErrorCode.NO_LICENSE ->
Log.d("Web Service is missing a valid License.")
LivenessErrorCode.PROCESSING_ATTEMPTS_ENDED ->
Log.d("Reached the number of possible attempts. See `LivenessConfiguration.attemptsCount` for more information.")
LivenessErrorCode.PROCESSING_FAILED ->
Log.d("Bad input data.")
LivenessErrorCode.PROCESSING_TIMEOUT ->
Log.d("Processing finished by timeout.")
LivenessErrorCode.API_CALL_FAILED ->
Log.d("Web Service API call failed due to networking error or backend internal error.")
LivenessErrorCode.CONTEXT_IS_NULL ->
Log.d("Provided context is null.")
LivenessErrorCode.IN_PROGRESS_ALREADY ->
Log.d("Liveness has already started.")
LivenessErrorCode.ZOOM_NOT_SUPPORTED ->
Log.d("Camera zoom support is required.")
LivenessErrorCode.CAMERA_NO_PERMISSION ->
Log.d("Application does not have camera permission.")
LivenessErrorCode.CAMERA_NOT_AVAILABLE ->
Log.d("Device has no available camera.")
LivenessErrorCode.PROCESSING_FRAME_FAILED ->
Log.d("Failed when Core could not recognize frame.")
}
}
LivenessErrorException exception = response.getException();
if (exception != null) {
switch (exeption.getErrorCode()) {
case LivenessErrorCode.CANCELLED:
Log.d("liveness", "User cancelled the processing.");
break;
case LivenessErrorCode.NO_LICENSE:
Log.d("liveness", "Web Service is missing a valid License.");
break;
case LivenessErrorCode.PROCESSING_ATTEMPTS_ENDED:
Log.d("liveness", "Reached the number of possible attempts. See `LivenessConfiguration.attemptsCount` for more information.");
break;
case LivenessErrorCode.PROCESSING_FAILED:
Log.d("liveness", "Bad input data.");
break;
case LivenessErrorCode.PROCESSING_TIMEOUT:
Log.d("liveness", "Processing finished by timeout.");
break;
case LivenessErrorCode.API_CALL_FAILED:
Log.d("liveness", "Web Service API call failed due to networking error or backend internal error.");
break;
case LivenessErrorCode.CONTEXT_IS_NULL:
Log.d("liveness", "Provided context is null.");
break;
case LivenessErrorCode.IN_PROGRESS_ALREADY:
Log.d("liveness", "Liveness has already started.");
break;
case LivenessErrorCode.ZOOM_NOT_SUPPORTED:
Log.d("liveness", "Camera zoom support is required.");
break;
case LivenessErrorCode.CAMERA_NO_PERMISSION:
Log.d("liveness", "Application does not have camera permission.");
break;
case LivenessErrorCode.CAMERA_NOT_AVAILABLE:
Log.d("liveness", "Device has no available camera.");
break;
case LivenessErrorCode.PROCESSING_FRAME_FAILED:
Log.d("liveness", "Failed when Core could not recognize frame.");
break;
}
}
const exception = response.exception;
if (exception) {
switch (exception.errorCode) {
case LivenessErrorCode.CANCELLED:
console.log('liveness: User cancelled the processing.');
break;
case LivenessErrorCode.NO_LICENSE:
console.log('liveness: Web Service is missing a valid License.');
break;
case LivenessErrorCode.PROCESSING_ATTEMPTS_ENDED:
console.log('liveness: Reached the number of possible attempts. See `LivenessConfiguration.attemptsCount` for more information.');
break;
case LivenessErrorCode.PROCESSING_FAILED:
console.log('liveness: Bad input data.');
break;
case LivenessErrorCode.PROCESSING_TIMEOUT:
console.log('liveness: Processing finished by timeout.');
break;
case LivenessErrorCode.API_CALL_FAILED:
console.log('liveness: Web Service API call failed due to networking error or backend internal error.');
break;
case LivenessErrorCode.CONTEXT_IS_NULL:
console.log('liveness: Provided context is null.');
break;
case LivenessErrorCode.IN_PROGRESS_ALREADY:
console.log('liveness: Liveness has already started.');
break;
case LivenessErrorCode.ZOOM_NOT_SUPPORTED:
console.log('liveness: Camera zoom support is required.');
break;
}
}
var exception = response?.exception;
if (exception != null) {
switch (exception.errorCode) {
case LivenessErrorCode.CANCELLED:
print('liveness: User cancelled the processing.');
break;
case LivenessErrorCode.NO_LICENSE:
print('liveness: Web Service is missing a valid License.');
break;
case LivenessErrorCode.PROCESSING_ATTEMPTS_ENDED:
print('liveness: Reached the number of possible attempts. See `LivenessConfiguration.attemptsCount` for more information.');
break;
case LivenessErrorCode.PROCESSING_FAILED:
print('liveness: Bad input data.');
break;
case LivenessErrorCode.PROCESSING_TIMEOUT:
print('liveness: Processing finished by timeout.');
break;
case LivenessErrorCode.API_CALL_FAILED:
print(
'liveness: Web Service API call failed due to networking error or backend internal error.');
break;
case LivenessErrorCode.CONTEXT_IS_NULL:
print('liveness: Provided context is null.');
break;
case LivenessErrorCode.IN_PROGRESS_ALREADY:
print('liveness: Liveness has already started.');
break;
case LivenessErrorCode.ZOOM_NOT_SUPPORTED:
print('liveness: Camera zoom support is required.');
break;
}
}
const exception = response.exception;
if (exception) {
switch (exception.errorCode) {
case FaceSDK.Enum.LivenessErrorCode.CANCELLED:
console.log('liveness: User cancelled the processing.');
break;
case FaceSDK.Enum.LivenessErrorCode.NO_LICENSE:
console.log('liveness: Web Service is missing a valid License.');
break;
case FaceSDK.Enum.LivenessErrorCode.PROCESSING_ATTEMPTS_ENDED:
console.log('liveness: Reached the number of possible attempts. See `LivenessConfiguration.attemptsCount` for more information.');
break;
case FaceSDK.Enum.LivenessErrorCode.PROCESSING_FAILED:
console.log('liveness: Bad input data.');
break;
case FaceSDK.Enum.LivenessErrorCode.PROCESSING_TIMEOUT:
console.log('liveness: Processing finished by timeout.');
break;
case FaceSDK.Enum.LivenessErrorCode.API_CALL_FAILED:
console.log('liveness: Web Service API call failed due to networking error or backend internal error.');
break;
case FaceSDK.Enum.LivenessErrorCode.CONTEXT_IS_NULL:
console.log('liveness: Provided context is null.');
break;
case FaceSDK.Enum.LivenessErrorCode.IN_PROGRESS_ALREADY:
console.log('liveness: Liveness has already started.');
break;
case FaceSDK.Enum.LivenessErrorCode.ZOOM_NOT_SUPPORTED:
console.log('liveness: Camera zoom support is required.');
break;
}
}
const exception = response.exception;
if (exception) {
switch (exception.errorCode) {
case LivenessErrorCode.CANCELLED:
console.log('liveness: User cancelled the processing.');
break;
case LivenessErrorCode.NO_LICENSE:
console.log('liveness: Web Service is missing a valid License.');
break;
case LivenessErrorCode.PROCESSING_ATTEMPTS_ENDED:
console.log('liveness: Reached the number of possible attempts. See `LivenessConfiguration.attemptsCount` for more information.');
break;
case LivenessErrorCode.PROCESSING_FAILED:
console.log('liveness: Bad input data.');
break;
case LivenessErrorCode.PROCESSING_TIMEOUT:
console.log('liveness: Processing finished by timeout.');
break;
case LivenessErrorCode.API_CALL_FAILED:
console.log('liveness: Web Service API call failed due to networking error or backend internal error.');
break;
case LivenessErrorCode.CONTEXT_IS_NULL:
console.log('liveness: Provided context is null.');
break;
case LivenessErrorCode.IN_PROGRESS_ALREADY:
console.log('liveness: Liveness has already started.');
break;
case LivenessErrorCode.ZOOM_NOT_SUPPORTED:
console.log('liveness: Camera zoom support is required.');
break;
}
}
Info
For more information on LivenessConfiguration
and LivenessResponse
, see the SDK Reference page.