• About TrustVision
  • Android SDK
  • iOS SDK
  • Flutter SDK
  • React Native SDK
  • API Client Libraries
  • eKYC Platform
  • Integration Case Studies
  • TS eKYC/FA App
TrustVision API Documentation

Version ≥ 5.x.x

Demo

Try the Web SDK

Install

CDN

html
<script src="https://vision-cdn.trustingsocial.com/tvweb-sdk/<VERSION>/build/tvweb-sdk.standalone.min.js"></script>

NOTE:

  • From version 5.22.0+, the SDK will load assets/resources from the Trusting Social CDN by default. If you want to self-host the SDK assets/resources, please refer to this section.
  • Using jsdelivr/unpkg CDN is not recommended as production builds may be affected by the stability of the CDN.
  • When using jsDeliver/unpkg CDN, @latest version is not recommended as production builds will be directly affected by version updates.

Setup using script tag

  • You can add the script tag to the public/index.html file of your project.
  • Or dynamically add the script tag to the head of your HTML file.
  • Applied for all frameworks (React, Angular, Vue, etc.)
html
<!-- add script to public/index.html -->
<script src="https://vision-cdn.trustingsocial.com/tvweb-sdk/<VERSION>/build/tvweb-sdk.standalone.min.js"></script>

Usage

Vanilla JS

Full example code here

javascript
const tv = new TVWebSDK.SDK({
  container: document.getElementById("container"),
  lang: "vi",
  enableAntiDebug: false,
});

// Main functions:

tv.runWarmUpReadIDCard(); // deprecated from version 5.7.4, please use runPreloadEKYCResources to replace

tv.runPreloadEKYCResources(); // Since 5.7.4+

tv.readIDCardUIOnly(params);
tv.readIDCardWithApiCall(params);

tv.livenessDetection(params);

React

javascript
import React, { useEffect, useRef } from "react";
function TVSDKExample() {
  const refTVWebSDK = useRef(null);
  useEffect(() => {
    refTVWebSDK.current = new TVWebSDK.SDK({
      container: document.getElementById("web-sdk-container"),
      lang: "vi",
    });
  }, []);
  // the rest example please follow Vanilla JS above
  return <div id="web-sdk-container" />;
}

Angular

javascript
// config
// in angular.json, add this to script
"scripts": [
  ...
  "node_modules/@tsocial/tvweb-sdk/build/tvweb-sdk.standalone.min.js"
  ...
]
// usage
declare var TVWebSDK: any;
// the rest example please follow Vanilla JS above

Vue

javascript
// Please follow Vanilla JS above

Constructor

Example:

javascript
const tv = new TVWebSDK.SDK({
  container: document.getElementById("container"),
  lang: "vi",
  enableAntiDebug: false,
});
RequiredTypeDefaultDescription
containeryesDOM ElementDOM element to render the UI
langnostringenlanguage to display (en or vi)
countrynostringvncountry
assetRootnostringDefault value is Trusting Social's CDNwhere to load images/icons.
enableAntiDebugnobooleantrueif enabled, sdk will try to prevent users from opening devtools in browsers
customUrlsnoobjectdefault custom urlsBesides assets, SDK also needs to load external resources (models, libraries...). This param allows to customize where to load those resources.
resourceRootnostringDefault value is Trusting Social's CDNSince version 5.7.8+, use this in case the client wants to bundle all resources from a root URL.
warmupMessagenoobject{ en: 'Warming up... Please wait a moment', vi: 'Đang khởi động, vui lòng chờ trong giây lát...' }Since version 5.8.3
themeVersionnostringv2This allows you to change between SDK available themes. Please refer to this section.
enableVoicenobooleanfalseAvailable from version 5.25.0. Enable voice instructions for SDK, only applied for Read ID Card, Flash Liveness and Passive Liveness V2

Note: For locale customization with lang and country, please refer to this section.

Default custom urls

The client can customize resource urls via customUrls parameter. How to customize these urls by your CDN. Please reference here

javascript
const customUrls = {
  blazefaceModelUrl:
    "https://storage.googleapis.com/tfhub-tfjs-modules/tensorflow/tfjs-model/blazeface/1/default/1/model.json",
  opencvUrl:
    "https://cdn.jsdelivr.net/npm/@tsocial/[email protected]/libs/opencv-4.6.0.js",
  idCardModelUrl:
    "https://cdn.jsdelivr.net/npm/@tsocial/[email protected]/models/id-card/tflite/model-v3.tflite",
  wechatQRModelsUrl:
    "https://cdn.jsdelivr.net/npm/@tsocial/[email protected]/wechat_qr_models/",
  tfScriptUrls: {
    tfUrl:
      "https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js",
    tfBackendWasmUrl:
      "https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf-backend-wasm.js",
    tfBackendCpuUrl:
      "https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]",
    tfBackendWebGlUrl:
      "https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]",
    tfLiteUrl:
      "https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf-tflite.min.js",
    blazefaceScriptUrl:
      "https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]",
  },
};
// And pass it to SDK via init method
const tv = new TVWebSDK.SDK({
  // ...
  customUrls: customUrls,
});

Notes for self-hosted decision

For these specific resources blazefaceModelUrl, opencvUrl, idCardModelUrl, tfUrl, tfBackendWasmUrl, tfBackendCpuUrl, tfBackendWebGlUrl, tfLiteUrl, blazefaceScriptUrl and SDK's resources (JS lib and assets):

Please contact the administrator to get the full resources tree and we will help the client to publish it to the client's CDN.

Note: We recommend the client use this option.

  • The client can host the resources of Web SDK together with your website on your CDN.
  • The client can control performance based on your traffic from your Website/CDN.

For example: after the resources are ready on your CDN. please use these settings.

javascript
const myCdnUrlPrefix = "<YOUR CDN URL>/";
const customUrls = {
  blazefaceModelUrl: `${myCdnUrlPrefix}models/1.0.0/blazeface/model.json`,
  opencvUrl: `${myCdnUrlPrefix}opencv/4.6.0/cv.js`,
  idCardModelUrl: `${myCdnUrlPrefix}models/1.0.0/id_card/tflite/model-v3.tflite`,
  wechatQRModelsUrl: `${myCdnUrlPrefix}models/1.0.0/webchat_qr/`,
  tfScriptUrls: {
    tfUrl: `${myCdnUrlPrefix}tfjs/3.20.0/tf.min.js`,
    tfBackendWasmUrl: `${myCdnUrlPrefix}tfjs/3.20.0/backend-wasm.js`,
    tfBackendCpuUrl: `${myCdnUrlPrefix}tfjs/3.20.0/backend-cpu.js`,
    tfBackendWebGlUrl: `${myCdnUrlPrefix}tfjs/3.20.0/backend-webgl.js`,
    tfLiteUrl: `${myCdnUrlPrefix}tflite/0.0.1-alpha.8/tflite.min.js`,
    blazefaceScriptUrl: `${myCdnUrlPrefix}blazeface/0.0.7/bf.js`,
  },
  idCardModelUrls: {
    model_vn: `${myCdnUrlPrefix}models/1.0.0/id_card/tflite/id_card_detector_v2_lite.tflite`,
    model_ph: `${myCdnUrlPrefix}models/1.0.0/id_card/tflite/ph_card_model.tflite`,
  },
};
const sdkVersion = "5.12.4";
const tv = new TVWebSDK.SDK({
  // other setting as SDK defined
  assetRoot: myCdnUrlPrefix + "tvweb-sdk/" + sdkVersion + "/assets",
  customUrls: customUrls,
});

Since version 5.7.9+: Client can use this option if the client host all resources from a CDN

javascript
const myCdnUrlPrefix = "YOUR CDN URL";
const sdkVersion = "5.12.4";
const tv = new TVWebSDK.SDK({
  // other setting as SDK defined
  assetRoot: myCdnUrlPrefix + "tvweb-sdk/" + sdkVersion + "/assets",
  resourceRoot: myCdnUrlPrefix, // only available from 5.7.9+
});

Read ID Card

runWarmUpReadIDCard (deprecated)

For the best user experience, this function should be called before calling readIDCardUIOnly or readIDCardWithApiCall to preload resources and warm up models. That way, this long process can be skipped when users start the journey.

Note: This method was deprecated from version 5.7.4. You can use runPreloadEKYCResources to replace it.

readIDCardUIOnly

To start a UI flow where we can get id card info from users (images, QR code, recorded videos...)

Params

RequiredTypeDefault
Description
clientSettingsyesobjectSettings to run SDK features. This object should have the same shape as settings from this api
onErroryesfunctionCallback when errors happen. An error object will be returned.
stepsnoobjectTVWebSDK.defaultReadIDCardStepsUI flow configuration
allowedCardTypesnoarray[](Only works if enabled in settings) SDK only allows certain card types. E.g. if it has value of ['vn.cccd'], SDK will alert that only CCCD card is allowed. Default [] or not pass in this value, SDK will accept all card types. Note: currently only apply for Vietnam from version 5.16.3, available values are listed below
detectIdCardnofunction() => Promise.resolve({ card_label: '' })This function receives { cardType, image: { blob, encrypted: { hex: <encrypted file encoded in hex string format> } }, cardSide } and should return or resolve to this object { card_label: <CARD_LABEL>}. Api to detect card label can be found here
onStepDonenoasync functionEmpty functionCallback every time users complete a step. onStepDoneResult will be returned. When you handle api in this function, please use async funtion (return Promise or using await) to wait the api complete before SDK move to next step, otherwise SDK will process to next step while the api is loading.
outputEncryptionSettingsnoobject{ key: '' }If key is provided, output files will be encrypted and returned via encrypted field. Please see here.
logCredentialsnoobject{}Config for tracking user's behavior while using
titlenostring''Only available for some custom build. Title of IDCard screen.
customTextsnoobject{}Only available from SDK version 5.21.0. Please refer to this section
onClosenofunctionEmpty functionCallback when users click the X button
flipVideoHorizontalnobooleannullFlip video horizontally. Please refer to this section

Allowed card types

Card typeDescription
vn.national_idCMND, CCCD, VN Passport
vn.cmnd_oldCMND old version
vn.cmnd_newCMND
vn.cccdCCCD
vn.cccd_newCCCD with chip
vn.tccTCC
vn.passportVN Passport
ph.national_idPH National ID card
global.passportGlobal passport

Read ID Card SDK result

onStepDoneResult has this shape (some fields may be missing depending on the settings and the specific step):

javascript
{
  stepNumber: number, // the current step number
  cardSide, // card side of current step
  cardType, // card type of current step
  image: {
    blob, // the captured image of ID card
    encrypted: {
      hex: string // will be returned if outputEncryptionSettings.key is provided
    }
  },
  error, // error of the captured image (if any). usually it is `id_detector_error_blur`, please check `Possible Error Objects` below.
  qrScannedResult: {
    result: string, // the detected QR code
    validateResult: {
      valid: boolean // whether the detected QR code follows new Vietnamese ID pattern
    },
    image: {
      blob, // QR image
      encrypted: {
        hex: string // will be returned if outputEncryptionSettings.key is provided
      }
    },
    recordedVideos: [ // recorded videos of QR capture process
      {
        id: string, // ignore
        frames: [] // array of frames in base64 format
      }
    ]
  },
  recordedVideos: [ // recorded videos of the whole process
      {
        id: string, // ignore
        frames: [] // array of frames in base64 format
      }
  ],
  apiResult // will be returned only if the SDK is called APIs by itself
}

Sample code

javascript
// Setup callback function to interact and receive results from Web SDK
// This function will be called when a user finishes capture ID card
function handleStepDoneIDCard({
  stepNumber,
  cardSide,
  cardType,
  image,
  qrScannedResult,
  recordedVideos,
  apiResult,
}) {
  console.log("card side:", cardSide); // Front or back
  console.log("card type:", cardType); // cmnd_old, cccd, cccd_new
  console.log("image", image);
  // The client should upload the image to server and call API to TrustingSocial
  // Show image to UI
  const imgEl = document.createElement("img");
  imgEl.width = "300";
  imgEl.src = createObjectURL(image.blob);

  if (qrScannedResult) {
    // QR raw code and QR image
    const { result, image } = qrScannedResult;
    // The client should upload the image to server and call API to TrustingSocial
    if (image && image.blob) {
      // Show QR image to UI
      const qrImgEl = document.createElement("img");
      qrImgEl.width = "200";
      qrImgEl.src = URL.createObjectURL(image.blob);
    }
  }
}

// This callback is called from SDK to get card type to continue flow in some special cases.
// Client should call this API https://ekyc.trustingsocial.com/api-reference/customer-api#detect-id-cards
async function handleDetectIdCard(props) {
  const { cardType, image, cardSide } = props;
  const apiClient = new trustvisionSdk.default(
    inputAccessKey.value,
    inputSecretKey.value,
    inputApiUrl.value
  );
  const resultUpload = await apiClient.uploadImage({
    file: image.blob,
    label: `id_card.${cardType}.${cardSide}`,
  });
  const imageId = resultUpload.data.image_id;
  const resultDetect = await apiClient.httpClient.post(
    "/v1/detect_id_cards_sync",
    {
      card_type: cardType,
      image: {
        id: imageId,
      },
    }
  );
  return _.get(resultDetect, "data.image.cards.0");
}

// Init parameters to capture ID card
const idCardProps = {
  onError: (e) => {
    // Handle error and destroy SDK
    tv.closeSDK();
  },
  onClose: () => {
    tv.closeSDK();
  },
  detectIdCard: handleDetectIdCard,
  onStepDone: handleStepDoneIDCard,
};
// Start capture ID Card
tv.readIDCardUIOnly(idCardProps);

readIDCardWithApiCall

This api works the same as UI only version with the addition of calling api by SDK.

Params

The same as these params with an addition of the followings:

RequiredType
Description
apiCredentialsyesApiCredentialsKeys to make api call
{ accessKey, secretKey, apiUrl }
serviceSettingsnoServiceSettingsSettings to enable or disable API service calls from SDK.
flowIdnoFlowIdFlow ID to get corresponding client settings

Log event config

Available from SDK version 5.13.8

Context:

We record logs for tracking user's behavior while using SDK. We will provide a log key beside api key but only used for sending log events. It is important to keep the log key secure, just like the API key. We have 2 ways to configure:

  1. Temporary credential

Your BE server will call this api to generate a temporary credential and return this temporary credential to client side, then pass this temporary credential to SDK.

javascript
logCredentials: {
  accessKey: 'YOUR ACCESS KEY',
  secretKey: 'YOUR SECRET KEY',
  apiUrl: 'LOG EVENT URL',
  userId: 'X-REQUEST-ID'
}
  1. Temporary signature

Your BE server will follow this guide to generate a temporary signature and return this temporary signature to client side, then pass this temporary signature to SDK.

javascript
logCredentials: {
  logSignature: {
    timestamp: '', // X-TV-Timestamp
    signature: '', // Authorization
    logUrl: '', // API url for logging event
  },
  userId: 'X-REQUEST-ID'
}

Note: Only generate 1 temporary credential/signature in 1 session for consistent logs.

Scan QR code

To start the QR scanning step, we can use the readIDCardUIOnly or readIDCardWithApiCall method same as above, but with the steps property as below:

javascript
const qrScanStep = {
  scannerType: TVWebSDK.ScannerType.QR_CODE,
  title: 'Quét mã QR trên CCCD',
  titleIcon: ''
  enableConfirmPopup: false,
};

const idCardProps = {
  ...
  steps: [qrScanStep],
};
tv.readIDCardUIOnly(idCardProps);

Liveness Detection

livenessDetection

This function starts liveness detection flow.

Params

A single object with these properties

Required
Description
TypeDefault
modeyesMode to operateLivenessMode
onLivenessDetectionDoneyesCallback when liveness detection flow is done.function(livenessResults:LivenessResults)
onErroryesCallback when errors happen.function(error: Error)
clientSettingsyesAvailable from v5.3.1.
This settings come from TS server and depend on your accessKey and secretKey.
When apiCheck enable, this setting will be automated get from TS server and bind to SDK. Anything pass to this props will overwrite settings come with apiCheck.
When you call api by your self, use this api to get client settings, and pass response to this props.
object
apiChecknoIf true, sdk will call APIs itself (apiCredentials is needed).booleanfalse
apiCredentialsnoAvailable from v5.3.1.
Keys to make api calls
ApiCredentials{
accessKey: "",
secretKey: "",
apiUrl: ""
}
customErrorsnoAvailable from v3.10.1.
Custom the errors show on SDK. See example below.
objectnull
onProcessingnoAvailable from v4.3.0.
From v4.3.0, SDK will resize output images to 400x400 before return. So this is the callback when SDK start resize images. Use to close camera or show loading. The resize process complete on onLivenessDetectionDone.
If apiCheck is enable, this callback also includes api call time.
functionEmpty function
onResetnoAvailable from v5.16.6.
Callback when liveness flow is reset. For example, when timeout happens, this callback will be triggered.
functionEmpty function
captureFrameSettingsnoAvailable from v5.3.1.
Settings for record sequence frames.
enable: On/Off record.
framesIntervalTime (millisecond): Adjust frames capture per second, only capture 1 frame in framesIntervalTime. When set to 0, SDK will capture full potential of device can handle.
framesBatchLength: If value is 0, only 1 array sequence frames return in onLivenessDetectionDone. If value greater than 0, SDK will return sequence frames by batch in onFramesCaptured with each batch have framesBatchLength length.
object{
enable: false,
framesIntervalTime: 180,
framesBatchLength: 0
}
onFramesCapturednoAvailable from v5.3.1.
The callback when SDK return sequence by batch.
functionEmpty function
outputEncryptionSettingsnoAvailable from v5.8.0.
If key is provided, output files will be encrypted and returned via encrypted field. Please see here.
object{ key: '' }
cameraScalenoAvailable from v5.9.0.
Scale up camera image in appropriate distance.
number ([1.0..2.0])1.0
offsetFaceYnoAvailable from v5.9.3.
Support large screen device (like Kiosk) to adjust "face area circle" for suitable user's height. Default is 0, pass a negative number to move "circle" to top, a positive number to move "circle" to bottom.
number (in pixel)0
logCredentialsnoConfig for tracking user's behavior while usingobject{}
titlenoOnly available for some custom build. Title of EKYC screen.string""
frontalMinSizenoAvailable from v5.13.9.
Scale last frontal image to frontalMinSize * frontalMinSize . The recommended value is 720. The scaled image will be returned in the frontalScaledImage field in LivenessResults`
numbernull
customTextsnoOnly available from SDK version 5.21.0. Please refer to this sectionobject{}
onClosenoCallback when users click the X buttonfunctionEmpty function
serviceSettingsnoSettings to enable or disable API service calls from SDK.ServiceSettings-
flipVideoHorizontalnoFlip video horizontally. Please refer to this sectionbooleannull
flowIdnoFlow ID to get corresponding client settingsFlowId""

Liveness Results

typescript
type LivenessResults = {
  steps: Array<{
    // gesture images
    name: String; // step name
    image: {
      blob: Blob;
      encrypted: {
        // will be returned if outputEncryptionSettings.key is provided
        hex: String; // encrypted image in base64 string
      };
      id: String; // id of image has been uploaded, appears only when apiCheck is enabled
    };
  }>;
  frontalFaces: Array<T>; // list of frontal image
  frontalFacesEncrypted: Array<String>; // list of frontal image encrypted in base64 string, will be returned if outputEncryptionSettings.key is provided
  frontalScaledImage: Blob; // the scaled frontal image if frontalMinSize was specified
  capturedFrames: Array<T>; // sequence frames array record liveness process.
  video: Blob; // the raw video of liveness process, return if turn on via access key settings
  apiCheckPassed: boolean; // whether all liveness checks passed, appears only when apiCheck is enabled.
  verifyFaceLivenessResult: LivenessResult<T>; // appears only when apiCheck is enabled.
  verifyFacePortraitResult: SanityResult<T>; // appears only when apiCheck is enabled.
};

Sample code

javascript
// Setup callback function to interact and receive results from Web SDK
// This function will be called when a user finishes capture selfie
function handleLivenessDetectionDone(result) {
  // Host site will use these parameters to call API verify liveness check
  // Reference API https://ekyc.trustingsocial.com/api-reference/customer-api#1-request-5
  const { frontalFaces, steps, capturedFrames } = result;
  // frontalFaces: list frontal images mapping with API param 'images'
  console.log("frontal faces: ", frontalFaces);
  frontalFaces.forEach((blob) => {
    // Upload list frontal image to server
  });
  /* steps: list gesture image mapping with API param 'gesture_images', each element contains params
        name: mapping to 'gesture'
        image: mapping to 'images'[0], always array with 1 element
    */
  console.log("gestures faces: ", steps);
  steps.forEach((s) => {
    // Upload list gesture image to server
  });
  // capturedFrames: list frames mapping with API param 'videos'
  // Upload frames batch to server
  console.log("video frames batch: ", capturedFrames);
}

const livenessDetectionMode = TVWebSDK.Constants.Mode.FLASH_16;
const livenessProps = {
  mode: livenessDetectionMode,
  onLivenessDetectionDone: handleLivenessDetectionDone,
  captureFrameSettings: {
    enable: true,
    framesIntervalTime: 180,
    framesBatchLength: 15,
  },
  onError: (e) => {
    // Handle error and close SDK
    console.log(e);
    tv.closeSDK();
  },
  frontCamera: true,
  onProcessing: () => {
    // Show loading
    setTimeout(() => {
      tv.closeSDK();
    }, 250);
  },
  onClose: () => {
    document.body.style.height = "auto";
    tv.closeSDK();
  },
  clientSettings,
};
// Start capture selfie
tv.livenessDetection(livenessProps);

Custom Errors

javascript
tv.livenessDetection({
  ...otherProps
  customErrors={{
    face_too_small: {
      code: 'face_too_small',
      msg: {
        en: 'Face too small',
        vi: 'Đưa camera lại gần',
      },
    },
    face_too_large: {
      code: 'face_too_large',
      msg: {
        en: 'Face too large',
        vi: 'Đưa camera ra xa',
      },
    },
  }}
})

List errors can custom:

javascript
no_face: {
  code: 'no_face',
  msg: {
    en: 'Face not found',
    vi: 'Không tìm thấy khuôn mặt',
  },
},
partial_face: {
  code: 'partial_face',
  msg: {
    en: 'Face out of the box',
    vi: 'Không tìm thấy toàn bộ khuôn mặt',
  },
},
multiple_faces: {
  code: 'multiple_faces',
  msg: {
    en: 'Too many faces',
    vi: 'Quá nhiều khuôn mặt',
  },
},
face_too_small: {
  code: 'face_too_small',
  msg: {
    en: 'Face too small',
    vi: 'Khuôn mặt quá nhỏ',
  },
},
face_too_large: {
  code: 'face_too_large',
  msg: {
    en: 'Face too large',
    vi: 'Khuôn mặt quá to',
  },
},
face_out_of_box: {
  code: 'face_out_of_box',
  msg: {
    en: 'Face is out of box',
    vi: 'Khuôn mặt không nằm trọn vẹn trong khung ảnh',
  },
},

Face Authentication

faceAuthentication

Available from version 5.18.0. This function starts face authentication flow.

Params

A single object with these properties

Required
Description
TypeDefault
modeyesMode to operatestring (one of [TVWebSDK.Constants.FaceAuthenticationMode.REGISTRATION, TVWebSDK.Constants.FaceAuthenticationMode.AUTHENTICATION])
authMethodyesAuthentication method to operatestring (one of [TVWebSDK.Constants.FaceAuthMethod.STANDARD_AUTHEN, TVWebSDK.Constants.FaceAuthMethod.ADVANCED_AUTHEN_ACTIVE, TVWebSDK.Constants.FaceAuthMethod.EDGE_AUTHEN, TVWebSDK.Constants.FaceAuthMethod.ADVANCED_AUTHEN_FLASH, TVWebSDK.Constants.FaceAuthMethod.LIGHT_AUTHEN])
onDoneyesCallback when face authentication flow is done.function(faceAuthenticationResult:FaceAuthenticationResult)
onErroryesCallback when errors happen.function(error: Error)
clientSettingsyesThis settings come from TS server and depend on your accessKey and secretKey.
When apiCheck enable, this setting will be automated get from TS server and bind to SDK. Anything pass to this props will overwrite settings come with apiCheck.
When you call api by your self, use this api to get client settings, and pass response to this props.
object
apiChecknoIf true, sdk will call APIs itself (apiCredentials is needed).booleanfalse
apiCredentialsnoKeys to make api callsApiCredentials{
accessKey: "",
secretKey: "",
apiUrl: ""
}
onClosenoCallback when users click the X buttonfunctionEmpty function
authTypenoAuthentication type, please see herestringtransfer
captureFrameSettingsnoSettings for record sequence frames.
enable: On/Off record.
framesIntervalTime (millisecond): Adjust frames capture per second, only capture 1 frame in framesIntervalTime. When set to 0, SDK will capture full potential of device can handle.
framesBatchLength: If value is 0, only 1 array sequence frames return in onLivenessDetectionDone. If value greater than 0, SDK will return sequence frames by batch in onFramesCaptured with each batch have framesBatchLength length.
object{
enable: false,
framesIntervalTime: 180,
framesBatchLength: 0
}
onFramesCapturednoThe callback when SDK return sequence by batch.functionEmpty function
outputEncryptionSettingsnoIf key is provided, output files will be encrypted and returned via encrypted field. Please see here.object{ key: '' }
logCredentialsnoConfig for tracking user's behavior while usingobject{}
flowIdnoFlow ID to get corresponding client settingsFlowId"face_authen"
serviceSettingsnoSettings to enable or disable API service calls from SDK.ServiceSettings-

Face Authentication Result

typescript
type FaceImage = {
    id?: string,
    base64?: string,
    blob: Blob,
    label: string,
    metadata?: string, // JSON String
    encrypted?: string, // Only availble when outputEncryptionSettings.key is provided
}

type VideoFrame = {
  index: number,
  base64: string,
  label: string,
  metadata?: string, // JSON String
}

type Video = {
  id?: string,
  metadata?: string, // JSON String
  frames: VideoFrame[]
}

type GestureFace = {
  gesture: 'up' | 'left' | 'right' | 'down',
  images: FaceImage[]
}

type FaceAuthenticationResult = {
  customerUserId: string,
  faces: FaceImage[],
  videos: Video[],
  gestureFaces: GestureFace[],
  selfieType: 'light' | 'passive' | 'active' | 'flash_edge' | 'flash_advanced',
  faceType: 'selfie' | 'id_card' | null, // only available on REGISTRATION mode
  apiResult: { // response from api
    data: {...}
  }
}

Integration with Face Authentication

Sample code

typescript
// Setup callback function to interact and receive results from Web SDK
// This function will be called when a user finishes face authentication flow
async function onFaceAuthenticationDone(result: FaceAuthenticationResult) {
  // Host site will use these parameters to call Face Authentication API
  const {
    customerUserId,
    faces,
    videos,
    gestureFaces,
    selfieType,
    faceType,
    apiResult,
  } = result;

  // Upload face images to server
  const uploadedFaceImages = (
    await Promise.all(
      faces.map((face) => {
        // Upload face image to server
      })
    )
  )
    .map
    // Map to correct API format
    ();

  // Upload gestureFaces to server
  const uploadedGestureImages = (
    await Promise.all(
      gestureFaces.map((gestureFace) => {
        // Upload gesture image to server
      })
    )
  )
    .map
    // Map to correct API format
    ();

  const apiPayload = {
    cus_user_id: customerUserId,
    faces: uploadedFaceImages,
    videos: videos,
    gesture_faces: uploadedGestureImages,
    selfie_type: selfieType,
    face_type: faceType, // Only use in REGISTRATION mode

    // Only use in AUTHENTICATION mode
    // Please refer https://ekyc.trustingsocial.com/api-reference/customer-api#sample-request-26:~:text=use%2Dcase%20differently.-,auth_type,-description
    auth_type: "",
  };

  // Call REGISTER or AUTHENTICATE API
  // API Reference
  // REGISTER: https://ekyc.trustingsocial.com/api-reference/customer-api#request-face-authentication-registration
  // AUTHENTICATE: https://ekyc.trustingsocial.com/api-reference/customer-api#request-face-authentication
  // Call API here...

  // For REGISTER only
  // If API call is successful, we will need to store the customerUserId for future use
  localStorage.setItem("tv:cus-user-id", customerUserId);
}

// Get SDK Client Settings from Server
// API Reference: https://ekyc.trustingsocial.com/api-reference/customer-api#get-client-settings
const clientSettings = {};

const mode = TVWebSDK.Constants.FaceAuthenticationMode.AUTHENTICATION;
const authMethod = TVWebSDK.Constants.FaceAuthMethod.EDGE_AUTHEN;

const faceAuthenProps = {
  mode: mode,
  authMethod: authMethod,
  onDone: onFaceAuthenticationDone,
  clientSettings: clientSettings,
  onError: (e) => {
    // Handle error and close SDK
    console.log(e);
    tv.closeSDK();
  },
  onClose: () => {
    document.body.style.height = "auto";
    tv.closeSDK();
  },
};

// Start Face authentication
tv.faceAuthentication(faceAuthenProps);

API Interface

LivenessMode

Type: string

ValueDescriptionVersion
flash_32Flash 32 mode5.18.0
flash_16Flash 16 mode5.18.0
flash_8Flash 8 mode5.18.0
passive_v2Passive V2 mode5.24.0
flashFlash mode5.14.0
flash_advancedFlash advanced mode5.18.0
flash_edgeFlash edge mode5.18.0
activeActive mode
passivePassive mode

ServiceSettings

Type: object

PropertyDescriptionTypeDefaultVersion
enableGetClientSettingsEnable/Disable get client settingsbooleantrue5.21.1
enableUploadFramesEnable/Disable upload framesbooleantrue5.19.0
enableUploadImagesEnable/Disable upload imagesbooleantrue5.19.0
enableVerifySanityPortraitEnable/Disable verify sanity for Portrait image servicebooleantrue5.19.0
enableVerifySanityIDCardEnable/Disable verify sanity for ID Card image servicebooleantrue5.19.0
enableVerifyFaceLivenessEnable/Disable verify face liveness servicebooleantrue5.19.0
enableDetectIDCardTamperingEnable/Disable detect ID Card tampering servicebooleantrue5.19.0
enableReadIDCardInfoEnable/Disable read ID Card info servicebooleantrue5.19.0
enableRegisterFaceEnable/Disable register face servicebooleantrue5.19.0
enableAuthenticateFaceEnable/Disable authenticate face servicebooleantrue5.19.0

Note: Besides enableGetClientSettings and enableUploadFrames, other services are only available when enableUploadImages is set to true.

ApiCredentials

Type: object

Keys to make API calls.

PropertyDescriptionTypeDefault
accessKeyAccess Keystring\"\"
secretKeySecret Keystring\"\"
apiUrlAPI base URLstring\"\"

FlowId

Type: string

ValueDescriptionVersion
face_authenValue for Face Authencation flow5.21.1
onboardingValue for Onboarding flow5.21.1

Utils

initNativeCamera

To open native camera instead of custom camera

RequiredTypeDefaultDescription
useFileBrowsenobooleanfalseoption to open file browse (gallery) or camera
acceptnostringimage/*this param specifies a filter for what file types the user can pick from the file input dialog box
frontCameranobooleanfalsewhich camera to open
onCaptureDonenofunctionEmpty functionCallback to return { file, encrypted: { hex } } where file is of type File and encrypted.hex (encrypted file encoded in hex string) will be returned if outputEncryptionSettings.key is provided
outputEncryptionSettingsnoobject{ key: '' }if key is provided, output files will be encrypted and returned as encrypted object as above

Return value: { openCamera } where openCamera is the function you can use to trigger opening camera. Example: https://unpkg.com/@tsocial/tvweb-sdk@latest/examples/native-camera/index.html

javascript
function handleCaptureDone({ file }) {
  imgResultEl.src = URL.createObjectURL(file);
  imgResultEl.onload = function () {
    URL.revokeObjectURL(this.src);
  };
}
const { openCamera } = tv.initNativeCamera({
  frontCamera: false,
  onCaptureDone: handleCaptureDone,
});
buttonOpenCamera.onclick = openCamera;

Notes: Some browsers don't open native camera directly at first. They'll show a popup to choose File Browser or Open Camera... instead.

runPreloadEKYCResources

For the best user experience, this function should be called before calling readIDCardUIOnly, readIDCardWithApiCall, livenessDetection to preload heavy resources and cache them. we should call this method when the first page was loaded. it will load resources in the background and not impact to user experience. Note: Supported since 5.7.4+

destroyView

Stop the camera immediately Sample code:

javascript
tv.destroyView();

closeSDK

Alias for destroyView

javascript
tv.closeSDK();

getListCamera (Working with multi-camera device)

Available from v5.9.0. Get list camera on deivce and choose the right camera. Get list camera:

javascript
tv.getListCamera().then((result) => {
  const cameras = result.filter(
    (device) => device.deviceId && device.kind === "videoinput"
  );
});

Output:

alt text

Use deviceId to open the desired camera

javascript
tv.readIDCardUIOnly({
...
// others props
defaultCameraId: deviceId
});
---
tv.livenessDetection({
...
// others props
defaultCameraId: deviceId
});

Flip camera horizontally

You can also choose to flip the camera stream horizontally by passing flipVideoHorizontal to readIDCardUIOnly or livenessDetection service. Its default value is null.

Note: This is only available from version 5.19.0

javascript
tv.readIDCardUIOnly({
...
// others props
flipVideoHorizontal: false
});
---
tv.livenessDetection({
...
// others props
flipVideoHorizontal: true
});

compareFaces

Compare face from readIDCard result & livenessDetection result.

Params: a single object with these properties

RequiredDescriptionTypeDefault
accessKey, secretKey, apiUrlyesKeys to make api callsstring
image1yesThe id of the front card image returns from onSuccess in readIDCard method.string
image2yesThe id of the first image returns from onLivenessDetectionDone (verifyFaceLivenessResult) in livenessDetection method.string
onSuccessyesSuccess callbackfunction
onErroryesError callbackfunction

Sample code:

javascript
tv.compareFaces({
  accessKey: "YOUR ACCESS KEY",
  secretKey: "YOUR SECRET KEY",
  apiUrl: "https://tv-staging.trustingsocial.com/api",
  image1: "image_id from upload API",
  image2: "image_id from upload API",
  onSuccess: (rs) => console.log(rs),
  onError: (e) => console.log(e),
});

getDeviceInfo

Only available from SDK version 5.18.0

An async function to get device information.

javascript
const metadata = await tv.getDeviceInfo();
console.log(metadata);

Device information will be returned as an object with the following properties (will return null if SDK cannot get information of that property):

PropertyValue
idstring or null
udidstring or null
snstring or null
imeistring or null
manufacturerstring or null
deviceNamestring or null
wlanMacstring or null
phoneNumberstring or null
location{ longitude: string or null, latitude: string or null }

Localization

Only available from SDK version 5.21.0

Each country has its default locales as follows:

CountryLocales availableDefault
Vietnam (vn)vi or envi
Philippines (ph)enen
India (in)enen

The default locale will be chosen based on the country and lang that are defined when you init the SDK. If you wish to overwrite the default texts, you can pass the customTexts object prop to your flow. The available texts to overwrite are listed below.

Note: Some previously customized texts/errors in older versions will be kept for backward compatibility. Please contact us if you need to update them.

KeyDefault en localeDefault vi localeNote
not_supportedCamera is not well-supported on this device/browser. Please try another one.Camera không được hỗ trợ. Vui lòng thử trình duyệt hoặc thiết bị khác.
camera_timeoutCan't access camera. Please check camera permission, try again or try another browser/deviceKhông thể truy cập camera. Vui lòng kiểm tra quyền truy cập camera, đóng các tab/app khác đang sử dụng camera hoặc thử lại trên trình duyệt/thiết bị khác.
wrong_orientationYou are using landscape mode. Please rotate screen to portrait mode and retry.Bạn đang sử dụng chế độ màn hình xoay ngang. Vui lòng quay màn hình theo chiều dọc và thực hiện lại.
unable_to_load_modelUnable to load model on this device/browser. Please try another one.Không thể load model. Vui lòng thử trình duyệt hoặc thiết bị khác.
no_permissionPlease grant the permission to access the camera.Vui lòng cấp quyền để truy cập camera.
no_faceFace not foundKhông tìm thấy khuôn mặt
partial_faceFace out of the boxKhông tìm thấy toàn bộ khuôn mặt
multiple_facesToo many facesQuá nhiều khuôn mặt
face_too_smallFace too smallKhuôn mặt quá nhỏ
face_too_largeFace too largeKhuôn mặt quá to
face_out_of_boxFace is out of boxKhuôn mặt không nằm trọn vẹn trong khung ảnh
liveness_too_fastPlease turn your face SLOWLYVui lòng quay CHẬM lại
liveness_terminatedPlease try again.Vui lòng thực hiện lại.
liveness_terminated_face_trackingYou need to turn your face SLOWLY. Please try again.Bạn cần quay CHẬM lại. Vui lòng thực hiện lại.
liveness_terminated_no_faceYour face was missing for too long. Please try again.Khuôn mặt không nằm trong khung hình. Xin vui lòng thử lại.
liveness_terminated_time_outYou need to complete gestures as instructed within <%= x %> seconds. Please try again.Bạn cần hoàn thành động tác theo chỉ dẫn trong <%= x %> giây. Xin vui lòng thử lại.Must include <%= x %> as-is to show countdown
id_detector_validate_angle_errorPlease place ID card straight, not tiltedVui lòng đặt giấy tờ thẳng, không bị nghiêng khi chụp
id_detector_no_cardsPlease capture the <%= side %> side of ID card (occupy 50-95% of the frame)Vui lòng chụp mặt <%= side %> của giấy tờ (chiếm 50-95% khung hình)Must include <%= side %> as-is to show card side
id_detector_card_too_smallPlease move ID card CLOSER (occupy 50-95% of the frame)Vui lòng đưa giấy tờ lại GẦN hơn (chiếm 50-95% khung hình)
id_detector_error_incompletePlease move ID card FARTHER (occupy 50-95% of the frame)Vui lòng đưa giấy tờ ra XA hơn (chiếm 50-95% khung hình)
glare_detectedGlare is detected. Please adjust the shooting angleẢnh bị chói / lóa. Vui lòng điều chỉnh góc chụp.
id_detector_error_front_side_no_facesPlease capture the front side of ID cardVui lòng chụp mặt trước của giấy tờ
id_detector_error_front_side_multiple_facesPlease capture the front side of one ID card only (remove other items out of the frame)Vui lòng chụp mặt trước 1 giấy tờ duy nhất
id_detector_error_back_side_has_facesPlease capture the back side of ID cardVui lòng chụp mặt sau của giấy tờ
id_detector_error_blurBlurry photo detected. Please capture again for a clearer imageẢnh bị mờ. Vui lòng chụp lại để có ảnh rõ nét hơn
not_frontal_facePlease look straight to camera.Vui lòng nhìn thẳng vào camera.
not_readableCamera already in use by another app/tab.Camera đang được sử dụng bởi ứng dụng/tab khác.
close_eyeOpen your eyes clearlyVui lòng mở to mắt
front_card_titleFront side of ID cardMặt trước CMND/CCCD
back_card_titleBack side of ID cardMặt sau CMND/CCCD
qr_titleScan QR code on ID cardQuét mã QR trên CCCD
front_card_descriptionPlease put the front side of ID card into the frameVui lòng đặt CMND mặt trước vào trong khung
back_card_descriptionPlease put the back side of ID card into the frameVui lòng đặt CMND mặt sau vào trong khung
btn_okOKTôi đã hiểu
frontfronttrước
backbacksau
btn_use_this_pictureUse this pictureDùng ảnh này
btn_try_againTry againThử lại
confirm_popup_valid_qrQR code scannedĐã quét được mã QR
confirm_popup_invalid_qrCould not scan QR codeKhông quét được mã QR
remaining_timeRemaining time: <%= x %> secondsThời gian còn lại: <%= x %> giâyMust include <%= x %> as-is to show countdown
timeout_instructionYou need to take the picture within <%= x %> seconds. Please try again.Bạn cần hoàn thành việc chụp giấy tờ trong <%= x %> giây. Vui lòng thử lại.Must include <%= x %> as-is to show countdown
warmup_introWarming up... Please wait a momentĐang khởi động, vui lòng chờ trong giây lát...
[TVWebSDK.Constants.FaceDirection.LEFT]Turn your face to the leftQuay mặt chậm qua trái
[TVWebSDK.Constants.FaceDirection.RIGHT]Turn your face to the rightQuay mặt chậm qua phải
[TVWebSDK.Constants.FaceDirection.UP]Turn your face upNgước mặt chậm lên trên
[TVWebSDK.Constants.FaceDirection.FRONTAL]Look straight into the cameraNhìn thẳng camera
qr_instructions_textPlease follow the instructions below to scan QR code:\n Move camera to the position of QR code on ID card. Align QR code to fit into camera frame to scan itVui lòng quét mã QR theo hướng dẫn sau:\n Đưa camera vào vị trí mã QR trên CCCD. Canh vừa mã QR vào 4 góc khung hình để quét mã
qr_instructions_start_buttonScan QR code
qr_instructions_countdownStart in <%= x %> sBắt đầu trong <%= x %> giâyMust include <%= x %> as-is to show countdown
qr_invalid_popup_titleInvalid QR codeQR không hợp lệ
qr_invalid_popup_descriptionPlease check and make sure scanning QR code on ID cardVui lòng kiểm tra và đảm bảo việc thực hiện quét QR code trên CCCD thật
qr_invalid_popup_retry_buttonScan QR (on ID card)Quét lại QR (trên CCCD)
qr_invalid_popup_skip_buttonSkipBỏ qua
qr_tooltipFit QR code to 4 corners of camera frame and try to move camera [in or out] to scan QR codeCanh vừa mã QR vào 4 góc khung hình và thử dịch chuyển camera [xa - gần] để quét được mã QR
session_timeout_popup_descriptionScan time exceeded. Please try again.\n Position the camera at the QR code on the card. Align the four corners and try moving the camera [far - near] so that the phone camera can focus more accurately.Quá thời gian quét. Vui lòng thực hiện lại.\n Đưa camera vào vị trí mã QR trên thẻ. Canh vừa vào 4 góc và thử dịch chuyển camera [xa - gần] để camera điện thoại lấy nét chuẩn hơn.
session_timeout_popup_retry_buttonRetryThử lại
session_timeout_popup_skip_buttonSkipBỏ qua

Usage example:

javascript
tv.readIDCardUIOnly({
  ... // others props
  customTexts: {
    front_card_title: 'My custom front card title',
    front_card_description: 'My custom front card description',
  }
});

tv.livenessDetection({
  ... // others props
  customTexts: {
    [TVWebSDK.Constants.FaceDirection.LEFT]: 'My custom turn left instruction',
    [TVWebSDK.Constants.FaceDirection.RIGHT]: 'My custom turn right instruction',
  }
});

Theme version

Only available from SDK version 5.19.0

This prop is used to change the theme of the SDK. The default theme is v2. The available themes are v1 and v2. Please see the images below for the differences between the two themes.

Default UI for v1

alt textalt textalt textalt textalt textalt textalt text

Default UI for v2

alt textalt textalt textalt textalt textalt textalt text

Browser compatibility

SDK works best on these major browsers (Chrome, Firefox, Safari):

Desktop

IEEdgeFirefoxChromeSafariOpera
>11>79>36>53>11>40

Mobile

iOS SafariFacbook Web Browser in iOSFacbook Web Browser in AndroidOpera miniOpera MobileChrome for AndroidChrome for iOSFirefox for AndroidUC Browser for Android
>11not supportednot supportednot supported>43, but there are some unexpected behaviors (browser ask to choose Front/Back camera)>85iOS>=14.3>79not supported

Please refer Browser compatibility on https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia for more information

WebView implementer notes

To make Web SDK work on a WebView, the WebView implementer must supports getUserMedia API and handle camera permission requests correctly.

For Android WebView, the implementer also needs to set the setMediaPlaybackRequiresUserGesture config to false. Otherwise, the camera video cannot be autoplay (the WebView requires a user gesture to play media).

For iOS WebView, there is a known issue which tracked here. You should take a look at that issue before you implement your WebView on iOS.

Highly recommendations

The default settings use CDNs to deliver assets/resources to user browsers, but Vietnamese internet access to those CDNs may encounter issues sometimes. To prevent that and improve user experience, we highly recommend hosting those resources in Vietnam. Please check default custom urls and notes for self-hosted decision.

Possible error objects

javascript
not_supported: {
  code: 'not_supported',
  msg: {
    en: 'Camera is not well-supported on this device/browser. Please try another one.',
    vi: 'Camera không được hỗ trợ. Vui lòng thử trình duyệt hoặc thiết bị khác.',
  },
},
camera_timeout: {
  code: 'camera_timeout',
  msg: {
    en: `Can't access camera. Please check camera permission, try again or try another browser/device`,
    vi:
      'Không thể truy cập camera. Vui lòng kiểm tra quyền truy cập camera, đóng các tab/app khác đang sử dụng camera hoặc thử lại trên trình duyệt/thiết bị khác.',
  },
},
unable_to_load_model: {
  code: 'unable_to_load_model',
  msg: {
    en: `Unable to load model on this device/browser. Please try another one.`,
    vi: 'Không thể load model. Vui lòng thử trình duyệt hoặc thiết bị khác.',
  },
},
no_permission: {
  code: 'no_permission',
  msg: {
    en: 'Please grant the permission to access the camera.',
    vi: 'Vui lòng cấp quyền để truy cập camera.',
  },
},
api_call_error: {
  code: 'api_call_error',
  msg: {
    en: 'Api call error',
    vi: 'Lỗi call api',
  },
},
upload_error: {
  code: 'upload_error',
  msg: {
    en: 'Upload error',
    vi: 'Lỗi upload',
  },
},
sanity_check_error: {
  code: 'sanity_check_error',
  msg: {
    en: 'Sanity check error',
    vi: 'Lỗi check sanity',
  },
},
read_id_card_error: {
  code: 'read_id_card_error',
  msg: {
    en: 'Read id card error',
    vi: 'Lỗi read id card',
  },
},
detect_id_tampering_error: {
  code: 'detect_id_tampering_error',
  msg: {
    en: 'Detect id tampering error',
    vi: 'Lỗi detect id tampering',
  },
},
detect_id_card_error: {
  code: 'detect_id_card_error',
  msg: {
    en: `Can't detect id card type`,
    vi: 'Không thể detect id card type',
  },
},
missing_front_id_card: {
  code: 'missing_front_id_card',
  msg: {
    en: 'Missing front id',
    vi: 'Không tìm thấy front id',
  },
},
server_error: {
  code: 'server_error',
  msg: 'Server error',
},
not_readable: {
  code: 'CameraInUseError',
  msg: {
    vi: 'Camera đang được sử dụng bởi ứng dụng/tab khác.',
    en: 'Camera already in use by another app/tab.',
  },
},
invalid_card_type_config: {
  code: 'invalid_card_type_config',
  msg: {
    en: 'Invalid card type config',
    vi: 'Cấu hình loại thẻ không hợp lệ',
  },
},
max_retry_reached: {
  code: 'max_retry_reached',
  msg: {
    vi: 'Đã quá số lần thử',
    en: 'Maximum retries reached',
  },
},
sdk_timeout_reached: {
  code: 'sdk_timeout_reached',
  msg: {
    vi: 'Đã quá thời gian thực hiện',
    en: 'SDK timeout reached',
  },
},
license_invalid: {
  code: 'license_invalid',
  msg: {
    en: 'Invalid license',
    vi: 'License không hợp lệ',
  },
},
license_rate_limit_exceeded: {
  code: 'license_rate_limit_exceeded',
  msg: {
    en: 'Invalid license',
    vi: 'Quá số lần thử',
  },
},
license_internal_server_error: {
  code: 'license_internal_server_error',
  msg: {
    en: 'Internal server error',
    vi: 'Lỗi server',
  },
},
license_unknown_error: {
  code: 'license_unknown_error',
  msg: {
    en: 'Unknown error',
    vi: 'Lỗi không xác định',
  },
},
{
  code: < DOMException > // refer: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Exceptions
}

Errors can be found in TVWebSDK.Constants.Errors

Integrate with TS server

Example and note when submit SDK's result to TS server.

Liveness Detection

Note: If captureFrameSettings is enabled, please refer to this section

  • Upload image by api /v1/images Example api call in js:
javascript
const payload = {
  label: 'portrait',
  metadata: JSON.stringify({
    gesture_score: <Double>, // image.leftRightScore
    gesture: <String>, // step name, 'frontal' if image is frontal
  }),
};
if (outputEncryptionSettings && outputEncryptionSettings.key) {
  apiClient.httpClient.post(
    '/v1/images',
    {
      base64: image.encrypted.hex,
      ...payload,
    }, // additional payload
    { 'X-TV-Key': outputEncryptionSettings.key } // additional header
  );
} else {
  apiClient.uploadImage({
    file: <Blob>,
    ...payload
  })
}
javascript
apiClient.verifyFaceLiveness({
  images: Array<{ // list 'frontalFaces' have been upload, please keep the order the same with origin `frontalFaces` array
    id: <String>, // imageId return from upload api
  }>,
  gesture_images: Array<{ // list 'steps' image have been upload
    gesture: <String>, // step name
    images: [{ id: <String> }], // imageId return from upload api
  }>,
  videos: [{ frames: capturedFrames }], // add this param if captureFrameSettings is enable
});
javascript
apiClient.verifyPortraitSanity({
  image: {
    id: <String>, // imageId of last frontal image in 'frontalFaces' have been uploaded
  },
});

Liveness Detection with captureFrameSettings

  • If captureFrameSettings is enabled, with framesBatchLength value > 0, it is recommended to use onFramesCaptured prop to catch sequence batch returned by SDK.
  • The callback in onFramesCaptured will be called whenever the SDK has captured the same number of frames that equals to framesBatchLength
  • With these early-returned batches of frames, we can upload those frames to get the file_id

Sample code for onFramesCaptured:

javascript
const uploadBatchPromises = [];
async function onFramesCaptured(frames) {
  const uploadPromise = uploadFrames({ frames });
  uploadBatchPromises.push(uploadPromise);
}
  • Then the videos param in verifyFaceLiveness api call won't need to add heavy frames. It just needs array of file ids.
  • Please note that you must await for the last batch of frames to be uploaded before proceeding to get result. It can be done by something similar to this:
javascript
const batchFileIds = await Promise.all(uploadBatchPromises).map((response) => ({
  id: response?.data?.file_id ?? -1,
}));
const verifyResult = await verifyFaceLiveness({
  images,
  gesture_images,
  videos: batchFileIds,
});
  • [OPTIONAL] When the flow is reset, by timeout or user has their face out of the frame for too long, we will need to reset the uploadBatchPromises array. And we can achieve that through onReset callback.
javascript
const onReset = () => {
  uploadBatchPromises.length = 0;
};

tv.livenessDetection({
  // ...
  onReset: onReset,
  // ...
});

Read ID Card

javascript
const { IDCardSide, Errors } = TVWebSDK.Constants;
async function uploadIdCard({
  image,
  cardType,
  cardSide,
  apiClient,
  outputEncryptionSettings,
}) {
  const payload = {
    label: `id_card.${cardType}.${cardSide}`,
  };
  if (outputEncryptionSettings && outputEncryptionSettings.key) {
    return apiClient.httpClient.post(
      "/v1/images",
      {
        base64: image.encrypted.hex,
        ...payload,
      }, // additional payload
      { "X-TV-Key": outputEncryptionSettings.key } // additional header
    );
  }
  return apiClient.uploadImage({
    file: image.blob,
    ...payload,
  });
}
async function uploadQRImage({
  image,
  apiClient,
  rawString,
  outputEncryptionSettings,
}) {
  const payload = {
    label: "qr_code",
    metadata: JSON.stringify({
      raw: rawString,
    }),
  };
  if (outputEncryptionSettings && outputEncryptionSettings.key) {
    return apiClient.httpClient.post(
      "/v1/images",
      {
        base64: image.encrypted.hex,
        ...payload,
      }, // additional payload
      { "X-TV-Key": outputEncryptionSettings.key } // additional header
    );
  }
  return apiClient.uploadImage({
    file: image.blob,
    ...payload,
  });
}
async function checkIdSanity({ imageId, cardType, apiClient }) {
  return apiClient.requestVerifyIDCard({
    card_type: cardType,
    image1: { id: imageId },
  });
}
async function detectIDTampering({
  apiClient,
  cardType,
  frontCardId,
  isCapturingFrontSide,
  backCardId,
  recordedVideos,
  qrImageId,
}) {
  const uploadedVideos = recordedVideos.filter((i) => i.id !== null);
  const pendingVideos = recordedVideos.filter(
    (i) => i.id === null && i.frames.length > 0
  );
  const finalUploadResult = await Promise.all(
    pendingVideos.map((i) =>
      apiClient.fileService.request({ frames: i.frames })
    )
  );
  const payload = {
    card_type: cardType,
    image: { id: frontCardId }, // this must be front side
    image2: isCapturingFrontSide ? undefined : { id: backCardId },
    videos: [
      ...uploadedVideos.map((i) => ({ id: i.id })),
      ...finalUploadResult.map((i) => ({ id: i.data.file_id })),
    ],
  };
  if (qrImageId) {
    payload.qr1_images = [
      {
        id: qrImageId,
      },
    ];
  }
  return apiClient.detectIDTampering(payload);
}
async function readIdCard({
  cardType,
  apiClient,
  frontCardId,
  backCardId,
  isCapturingFrontSide,
  qrImageId,
}) {
  const payload = {
    card_type: cardType,
    image1: isCapturingFrontSide ? { id: frontCardId } : { id: backCardId },
  };
  if (qrImageId) {
    payload.qr1_images = [
      {
        id: qrImageId,
      },
    ];
  }
  return apiClient.readIDCard(payload);
}
export default async function handleApiCheck({
  image,
  frontCardId: existingFrontCardId,
  qrImageId: existingQRImageId,
  onError,
  recordedVideos,
  apiClient,
  cardType,
  cardSide,
  qrScannedResult,
  outputEncryptionSettings,
}) {
  if (cardSide === IDCardSide.BACK && !existingFrontCardId) {
    onError(Errors.missing_front_id_card);
    return;
  }
  const uploadPromises = [
    uploadIdCard({
      image,
      cardType,
      cardSide,
      apiClient,
      outputEncryptionSettings,
    }),
  ];
  if (qrScannedResult && qrScannedResult.image && qrScannedResult.result) {
    uploadPromises.push(
      uploadQRImage({
        image: qrScannedResult.image,
        apiClient,
        rawString: qrScannedResult.result,
        outputEncryptionSettings,
      })
    );
  }
  const [uploadIdCardResult, uploadQrResult] = await Promise.all(
    uploadPromises
  );
  if (uploadIdCardResult.errors) {
    onError({ ...Errors.upload_error, details: uploadIdCardResult.errors });
    return;
  }
  const imageId = uploadIdCardResult.data.image_id;
  const qrImageId = uploadQrResult?.data?.image_id || existingQRImageId;
  let frontCardId;
  let backCardId;
  const isCapturingFrontSide = cardSide === IDCardSide.FRONT;
  if (isCapturingFrontSide) {
    frontCardId = imageId;
  } else {
    frontCardId = existingFrontCardId;
    backCardId = imageId;
  }
  const sanityResult = await checkIdSanity({ imageId, cardType, apiClient });
  if (sanityResult.errors) {
    onError({ ...Errors.sanity_check_error, details: sanityResult.errors });
    return;
  }
  const readIdCardResult = await readIdCard({
    cardType,
    apiClient,
    frontCardId,
    backCardId,
    isCapturingFrontSide,
    qrImageId,
  });
  if (readIdCardResult.errors) {
    onError({ ...Errors.read_id_card_error, details: readIdCardResult.errors });
    return;
  }
  const detectIDTamperingResult = await detectIDTampering({
    apiClient,
    cardType,
    frontCardId,
    isCapturingFrontSide,
    backCardId,
    recordedVideos,
    qrImageId,
  });
  if (detectIDTamperingResult.errors) {
    onError({
      ...Errors.detect_id_tampering_error,
      details: detectIDTamperingResult.errors,
    });
    return;
  }
  return {
    sanityResult: sanityResult.data,
    tamperingInfo: detectIDTamperingResult.data,
    cardInfo: readIdCardResult.data,
  };
}

With Encryption

If client uses outputEncryptionSettings prop in Read ID Card, Liveness Detection and Face Authentication, SDK will encrypt images and frames with the given key, and return the encrypted data on SDK done.

For Images

The encrypted image in return via the encrypted field, as described in the SDK Results sections for Read ID Card and and Liveness Detection.

When client receives the encrypted image, they can upload image as described above, and they MUST add the X-TV-Key header with the same value of SDK's outputEncryptionSettings.key to decrypt the image on the server side.

For Frames (available from SDK version 5.18.0)

The data that need to be encrypted in each frame are base64 and metadata (currently only Flash liveness frames have metadata field, Active and Passive liveness frames only have base64). If encryption is enabled, the values of base64 and metadata will be the encrypted data.

When client receives the encrypted frames, they can upload frames, and they MUST add the X-TV-Key header with the same value of SDK's outputEncryptionSettings.key to decrypt the frames on the server side.

Using SDK Adapter

Only available from SDK version 5.21.0

The SDK Adapter helps you integrate the whole Ekyc flow faster without having to worry about the details of the APIs. You can extend our adapter class, and overwrite the interfaces with whichever method you want to call our services (Whether it is direct calls to our APIs or through your own backend). After overwriting the interfaces, you can pass the SDK Adapter instance as a prop when you start the Ekyc full flow.

Use cases

The default case is to call to TS server directly, as illustrated below:

alt text

You can also overwrite the interfaces to make the SDK Adapter call your own backend instead of calling our APIs directly, as illustrated below:

alt text

Configurations by keys

There are 2 main configurations that can be set up by client's key settings (please contact us for more information about these setups):

  • Desired services: Adapter will only call the services that are enabled in the client's key settings
  • Number of retries: Adapter will retry the failed services with the number of retries set up in the client's key settings, or 3 times by default

Properties

Properties for the Ekyc Fullflow are as follows:

RequiredDescriptionNote
sdkAdapteryesSDK Adapter instance
flownoDesired ekyc flowDefault: full flow TVWebSDK.Constants.EkycFlow.FULL_FLOW. You can pass in TVWebSDK.Constants.EkycFlow.ID_CARD for ID card only, or TVWebSDK.Constants.EkycFlow.FACE for face only.
logCredentialsnoConfig for tracking user's behavior while using
readIDCardPropsnoProps for read ID card, check here.Skip these props onStepDone, onError, because the fullflow adapter will handle it.
livenessDetectionPropsnoProps for liveness detection, check here.Skip these props apiCheck, apiCredentials, onLivenessDetectionDone, onError, because the fullflow adapter will handle it.
onEkycDonenoThe callback will return all service results following this format
onErrornoError callback

Interfaces

Tvweb-sdk provides default implementations for the interfaces. You can overwrite the interfaces with your own implementation. Below are the interfaces, the output format, and the required return fields of each interface.

ServiceOutput FormatRequired return fields
clientSettingsSame output as hereMust return as is
uploadImageSame output as hereMust return as is
uploadFilesSame output as hereMust return as is
requestVerifyIDCardCustom return format with API resultdata.card_sanity.verdict,
data.status,
errors
detectIDTamperingCustom return format with API resultdata.card_tampering.verdict,
data.status,
errors
readIDCardCustom return format with API resultdata.card_information,
data.status,
errors
requestVerifyPortraitCustom return format with API resultdata.portrait_sanity.verdict,
data.status,
errors
verifyFaceLivenessCustom return format with API resultdata.is_live,
data.status,
errors
compareFacesCustom return format with API resultdata.compare_faces,
data.status,
errors
searchFacesCustom return format with API resultdata.faces,
errors
indexFacesCustom return format with API resultMust return as is

Note: You will have to override all interfaces in order to use the Adapter properly. However if you wish to only override some, you will have to pass in your credentials, including accessKey, secretKey, apiUrl. With this, SDK Adapter will use the default implementations for the ones you don't override, and use your custom interfaces for the ones you do.

Interface return format

SDK Adapter allows you to customize the messages in popup if encounter failure during the Ekyc flow. You don't have to pass in the message prop if not needed, we also have the default popup content. But if you wish to customize the popup content, please follow the return format as below:

javascript
{
  result, // the return data from the corresponding API
  message: { // optional
    error: {
      en: '', // custom english message for failed case
      vi: '', // custom vietnamese message for failed case
    },
    success: {
      en: '', // custom english message for successful case
      vi: '', // custom vietnamese message for successful case
    },
  }
}

// Example for requestVerifyIDCard
{
  result: {
    data: {
      card_sanity: {
        verdict: 'PASS',
      },
      status: 'SUCCESS',
    },
  },
  message: {
    error: {
      en: 'Verify card sanity failed',
      vi: 'Kiểm tra sanity thất bại',
    },
    success: {
      en: 'Verify card sanity successfully',
      vi: 'Kiểm tra sanity thành công',
    },
  },
}

Note: In the result prop, it is best to return as is to the SDK Adapter. But if you don't want to expose all data from APIs, SDK Adapter will only need some custom fields. Please refer to the interfaces table to know which fields are needed for each API. You must return the required fileds in the same format as the corresponding API for the SDK Adapter to work properly.

Ekyc flow return format

The SDK adapter will return the response after the Ekyc flow is done with the following format:

{
  frontCard: // see Card result section below
  backCard: // see Card result section below
  faceLiveness: // see Face liveness result section below
  compareFaces: // see Compare faces result section below
  searchFaces: // see Search faces result section below
  indexFaces: // see Index faces result section below
}

Note: Please check the Response section of each service here for more details of the returned data.

Card result format

javascript
{
  stepNumber: number,
  cardSide: string,
  cardType: string,
  image: {
    blob,
    encrypted,
    detectedCard,
  },
  recordedVideos: [],
  apiResult: {
    qrImageId: string,
    frontCardId: string,
    backCardId: string,
    sanityResult: {}, // data field from response of Request verify ID card
    tamperingInfo: {}, // data field from response of Request detect ID card tampering
    cardInfo: {}, // data field from response of Read ID card info
  }
}

Face liveness format

javascript
{
  frontalFaces: [
    {
      id: string,
      blob
    }, ...
  ],
  steps: [
    {
      name: string,
      id: string,
      blob
    }, ...
  ],
  capturedFrames: [],
  verifyFacePortraitResult: {}, // data field from response of Request verify portrait
  verifyFaceLivenessResult: {}, // data field from response of Verify face liveness
}

Compare faces format

Data field from response of Compare faces

Search faces format

Data field from response of Search faces

Index faces format

Data field from response of Index faces

Sample code

  1. In this example, we will use Adapter call to TS server directly, using apiCredentials. With apiCredentials, the Adapter will handle everything.
javascript
const accessKey = 'YOUR ACCESS KEY';
const secretKey = 'YOUR SECRET KEY';
const apiUrl = 'YOUR API URL';

class MyAdapter extends TVWebSDK.SDKAdapter {}
const adapter = new MyAdapter(accessKey, secretKey, apiUrl);

// Pass the SDK Adapter instance as a prop when you start the Ekyc full flow
tv.ekycFlow({
  sdkAdapter: adapter,
  livenessDetectionProps: {...},
  readIDCardProps: {...}
  onDoneEkyc: (result) => console.log(result),
  onError: (e) => console.log(e),
});
  1. In this example, we will use Adapter call to TS server directly, using apiClient to overwrite the interfaces. You can check out the apis here.
javascript
// create apiClient instance with your access key and secret key, pass in X-Request-Id header
const apiClient = new trustvisionSdk.default(
  'accessKey',
  'secretKey',
  'apiUrl',
  { httpHeaders: { 'x-request-id': 'xRequestId' } }
);
// Extend the SDKAdapter class and overwrite the interfaces
class MyAdapter extends TVWebSDK.SDKAdapter {
  async clientSettings() {
    const result = await apiClient.clientSettings();
    return result;
  }
  async uploadImage(params) {
    const result = await apiClient.uploadImage(params);
    return result;
  }
  async uploadFiles(params) {
    const result = await apiClient.fileService.request(params);
    return result;
  }
  async requestVerifyIDCard(params) {
    const result = await apiClient.requestVerifyIDCard(params);
    const customResult = { // only return the required fields
      data: {
        card_sanity: {
          verdict: result?.data?.card_sanity?.verdict,
        },
        status: result?.data?.status,
      },
    };
    return {
      result: customResult,
      message: { // custom popup message
        error: { en: 'requestVerifyIDCard error en', vi: 'requestVerifyIDCard error vi' },
        success: { en: 'requestVerifyIDCard success en', vi: 'requestVerifyIDCard success vi' },
      },
    };
  }
  async detectIDTampering(params) {
    const result = await apiClient.detectIDTampering(params);
    return { result };
  }
  async readIDCard(params) {
    const result = await apiClient.readIDCard(params);
    return { result };
  }
  async requestVerifyPortrait(params) {
    const result = await apiClient.requestVerifyPortrait(params);
    return { result };
  }
  async verifyFaceLiveness(params) {
    const result = await apiClient.verifyFaceLiveness(params);
    return { result };
  }
  async compareFaces(params) {
    const result = await apiClient.compareFaces(params);
    return { result };
  }
  async searchFaces(params) {
    const result = await apiClient.searchFaces(params);
    return { result };
  }
  async indexFaces(params) {
    const result = await apiClient.indexFaces(params);
    return { result };
  }
}
// Pass the SDK Adapter instance as a prop when you start the Ekyc full flow
const adapter = new MyAdapter();

tv.ekycFlow({
  sdkAdapter: adapter,
  livenessDetectionProps: {...},
  readIDCardProps: {...}
  onDoneEkyc: (result) => console.log(result),
  onError: (e) => console.log(e),
});
  1. In this example, we will integrate the SDK Adapter by calling through bank's backend. Please note that the return format of the interfaces must be the same as defined in the interfaces table.
javascript
class MyAdapter extends TVWebSDK.SDKAdapter {
  async clientSettings() {
    const result = await bankBackend.getClientSettings();
    return result;
  }
  async uploadImage(params) {
    const result = await bankBackend.uploadImage();
    return result;
  }
  async requestVerifyIDCard(params) {
    const result = await bankBackend.requestVerifyIDCard(params);
    const customResult = { // must return the required fields
      data: {
        card_sanity: {
          verdict: 'good',
        },
        status: 'success',
      },
    };
    return {
      result: customResult,
      message: { // custom popup message
        error: { en: 'requestVerifyIDCard error en', vi: 'requestVerifyIDCard error vi' },
        success: { en: 'requestVerifyIDCard success en', vi: 'requestVerifyIDCard success vi' },
      },
    };
  }
  ... // similar for other interfaces.
}

// Pass the SDK Adapter instance as a prop when you start the Ekyc full flow
tv.ekycFlow({
  sdkAdapter: adapter,
  livenessDetectionProps: {...},
  readIDCardProps: {...}
  onDoneEkyc: (result) => console.log(result),
  onError: (e) => console.log(e),
});