Bytid liveness for Flutter

Bytid was called UVerify (and, briefly, ByteID). Package names are unchanged, and UVerify and ByteID still work as aliases of Bytid.

Bytid's face check (liveness), and verification links, inside your Flutter app.

The check runs on Bytid's hosted page, the same one your customers get in a browser, so every anti-spoofing layer (depth, colour flash, random prompts) applies, and improvements reach your app without an app update.

flutter pub add uverify_liveness

iOS 14.3+ (camera inside web views), Android 7+.

Setup

iOS: ios/Runner/Info.plist

<key>NSCameraUsageDescription</key>
<string>We use the camera to confirm it's really you.</string>

and in ios/Podfile, turn on the camera permission for permission_handler:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'PERMISSION_CAMERA=1']
    end
  end
end

Android: android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.CAMERA" />

Using address links? Also add NSLocationWhenInUseUsageDescription to Info.plist and <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> to the manifest. If the link asks for a proof of address, the customer photographs it: add NSPhotoLibraryUsageDescription (iOS) for the gallery; image_picker handles the rest.

Use

Your server creates the session with a Bytid server SDK and returns its url. Never put your API key in the app.

import 'package:uverify_liveness/uverify_liveness.dart';

final result = await UVerifyLiveness.start(context, url: session.url);

switch (result) {
  case LivenessResult(:final sessionId, :final status):
    // status: passed, failed or expired. Send sessionId to your server for the face match.
    break;
  case UVerifyCancelled(:final reason):
    // closed, back or camera_denied
    break;
  default:
    break;
}

Then, on your server, run the face match with that session:

await bytid.identity.bvnFaceMatch({ id_number, first_name, last_name, liveness_session_id: sessionId });

The result in the app is only a hint to move on; trust what your server gets from the API.

To place the check in your own screen instead of a full-screen route, use the widget:

UVerifyLivenessView(url: session.url, onResult: (result) { /* ... */ })

The same view opens a verification link (link.url from kyc.createLink); the result is a KycResult with id, status and outcome.

It also opens an address link (link.url from address.createLink), where the customer shares their phone's location at home. The view asks for location instead of the camera, and the result is an AddressResult with id, status and result (verified, partial or failed). Read the distance and details on your server with address.getLink(id). A refused location comes back as UVerifyCancelled('location_denied').

Options

url The session or link url from your server
redirectUrl The redirect_url you created it with, if any; reaching it also finishes (RedirectResult with its query params)
allowedHosts Extra hosts allowed in the view (Bytid's are always allowed; everything else is blocked)
title UVerifyLiveness.start only: the app bar title

API reference: https://www.bytid.com/docs. MIT licence.

Libraries

uverify_liveness
Bytid (formerly UVerify) face check (liveness) and verification links inside a Flutter app.