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

Version ≥ 5.x.x

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

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

tv.runPreloadEKYCResources();

React

javascript
import React, { useEffect, useRef } from "react";
function TVSDKExample() {
  const sdkInstanceRef = useRef(null);

  useEffect(() => {
    sdkInstanceRef.current = new TVWebSDK.SDK({
      container: document.getElementById("web-sdk-container"),
      lang: "vi",
      enableAntiDebug: false,
    });

    sdkInstanceRef.current.runPreloadEKYCResources();
  }, []);

  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
langnoLanguageCodeenlanguage to display (en or vi)
assetRootnostringDefault value is Trusting Social's CDNwhere to load images/icons.
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.
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.
themeVersionnostringv2This allows you to change between SDK available themes. Please refer to this section.
countrynostringvncountry
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

readIDCardUIOnly

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

Params

Required
Type
Default
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.
stepsnoIDCardStep[]TVWebSDK.defaultReadIDCardStepsUI flow configuration. Check IDCardStep
allowedCardTypesno(string | CardType)[][](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. Available since version 5.19.0

Allowed card types

Can be either below list of predefined string or CardType object type:

  • CardType object can be used to extend new card types that are not in the predefined list.
  • All vn.* card types cannot be used with other card types that don't start with vn..
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
typescript
type CardType = {
  back_qr: {
    exist: boolean; // whether back side has QR code
  };
  code: string; // card type code
  front_qr: {
    exist: boolean; // whether front side has QR code
  };
  has_back_side: boolean; // whether card has back side
  front_face: {
    exist: boolean; // whether front side has face photo
  };
  back_face: {
    exist: boolean; // whether back side has face photo
  };
  name: string; // card type name
  orientation: "horizontal" | "vertical"; // card orientation
};

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
serviceSettingsnoServiceSettingsSettings to enable or disable API service calls from SDK.
flowIdnoFlowIdFlow ID to get corresponding client settings

Read ID Card SDK result

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

typescript
type ReadIDCardResult = {
  stepNumber: number; // the current step number
  cardSide: "front" | "back"; // card side of current step
  cardType: string; // card type of current step
  image: {
    blob: Blob; // the captured image of ID card
    encrypted?: {
      hex: string; // will be returned if outputEncryptionSettings.key is provided
    };
  };
  error?: any; // 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: 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
    }
  ];
  fullVideo?: any;
  capturingVideo?: any;
  apiResult?: {
    sanityResult: any;
    tamperingInfo: any;
    cardInfo: any;
    cardImageId: string;
    qrImageId: string;
    mp4VideoIds: {
      full: string;
      capturing: string;
    };
  }; // will be returned only if the SDK is called APIs by itself
};

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: 'qr_code', // This is the scanner type to scan QR code
  title: 'Quét mã QR trên CCCD',
  titleIcon: ''
  enableConfirmPopup: false,
};

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

Integration with Read ID Card

  • The callback onStepDone will be invoked whenever a card image is captured. You can check the response data structure here

Upload ID Card Image

  • Upload Image API: /v1/images
  • You will receive the uploaded image ID when completed
typescript
const onStepDone = async ({ cardSide, cardType, image }) => {
  const uploadIDResponse = await API.uploadImage({
    file: image.blob,
    label: `id_card.${cardType}.${cardSide}`,
  });

  // If this front side, you must store the imageId so when capturing back side, you can pass it to verify tampering API
  const imageId = uploadIDResponse.file_id; // Sample response structure
};

Upload QR Image

  • Upload Image API: /v1/images
  • If the QR code is scanned successfully on the ID Card, the QR image will be returned by the SDK in qrScannedResult
typescript
const onStepDone = async ({ qrScannedResult }) => {
  const uploadQRResponse = await API.uploadImage({
    file: qrScannedResult.image.blob,
    label: "qr_code",

    // Optional metadata field, you can add any custom info here
    metadata: JSON.stringify({
      raw: qrScannedResult.result,
    }),
  });

  // You need to store the QR imageId for detect tampering API
  const qrImageId = uploadQRResponse.file_id; // Sample response structure
};

Check Sanity of the image

typescript
const onStepDone = async ({ cardType, image }) => {
  // ... upload ID card image first to get imageId
  const sanityCheckResult = await API.verifyIDCardSanity({
    card_type: cardType,
    image1: { id: imageId },
  });
};

Check ID Tampering

typescript
const onStepDone = async ({ cardType, cardSide }) => {
  // ... upload front/back ID card images and QR image first to get their IDs

  const detectIDTamperingResult = await API.detectIDTampering({
    card_type: cardType,
    image: { id: frontCardId }, // this must be front side
    qr1_images: qrImageId ? [{ id: qrImageId }] : undefined,

    // Only add image2 when capturing back side
    image2: cardSide === "back" ? { id: backCardId } : undefined,
  });
};

Read ID Card Info

typescript
const onStepDone = async ({ cardType }) => {
  // ... upload front/back ID card images and QR image first to get their IDs

  const readIDCardResult = await API.readIDCardInfo({
    card_type: cardType,
    image1: cardSide === "back" ? { id: backCardId } : { id: frontCardId },
    qr1_images: qrImageId ? [{ id: qrImageId }] : undefined,
  });
};

Full sample code for Read ID Card flow

  • The sample code below demonstrates the full flow of Read ID Card with all API calls integrated.
typescript
let frontImageId = null;
let backImageId = null;
let qrImageId = null;

const onStepDone = async ({ cardType, cardSide, image, qrScannedResult }) => {
  // Upload ID Card image
  const uploadIdCardResponse = await API.uploadImage({
    file: image.blob,
    label: `id_card.${cardType}.${cardSide}`,
  });
  const imageId = uploadIdCardResponse.file_id; // Sample response structure

  // Upload QR image if available
  if (qrScannedResult && qrScannedResult.image && qrScannedResult.result) {
    const uploadQRResponse = await API.uploadImage({
      file: qrScannedResult.image.blob,
      label: "qr_code",

      // Optional metadata field, you can add any custom info here
      metadata: JSON.stringify({
        raw: qrScannedResult.result,
      }),
    });
    qrImageId = uploadQRResponse.file_id; // Sample response structure
  }

  // Store front/back image IDs
  if (cardSide === "front") {
    frontImageId = imageId;
  } else {
    backImageId = imageId;
  }

  // Check ID Sanity
  const sanityCheckResult = await API.verifyIDCardSanity({
    card_type: cardType,
    image1: { id: imageId },
  });
  if (sanityCheckResult.errors) {
    // Handle sanity error
  }

  // Detect ID Tampering
  const detectIDTamperingResult = await API.detectIDTampering({
    card_type: cardType,
    image: { id: frontImageId }, // this must be front side
    image2: cardSide === "back" ? { id: backImageId } : undefined,
    qr1_images: qrImageId ? [{ id: qrImageId }] : undefined,
  });
  if (detectIDTamperingResult.errors) {
    // Handle detect tampering error
  }

  // Read ID Card Info
  const readIDCardResult = await API.readIDCardInfo({
    card_type: cardType,
    image1: cardSide === "back" ? { id: backImageId } : { id: frontImageId },
    qr1_images: qrImageId ? [{ id: qrImageId }] : undefined,
  });
  if (readIDCardResult.errors) {
    // Handle read ID card error
  }
};

tv.readIDCardUIOnly({
  // ...
  onStepDone: onStepDone,
  // ...
});

Front/Back step separation

  • There are mainly 2 types of flow for reading ID Card
    • Full front/back scanning within 1 SDK session
    • Front/back scanning is splitted into separated sessions
  • Full front/back scanning within 1 SDK session is demonstrated in the sample code above.
  • In order to separate front/back/qr scanning into different sessions, you can use steps parameter to configure the scanning flow
  • You can check the IDCardStep type here
typescript
const frontStep = {
  scannerType: "id_card",
  cardSide: "front",
};
const backStep = {
  scannerType: "id_card",
  cardSide: "back",
};
const qrStep = {
  scannerType: "qr_code",
};

tv.readIDCardUIOnly({
  // ...
  steps: [frontStep, qrStep, backStep], // configure the steps as needed
  onStepDone: onStepDone,
  // ...
});

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. See more here
functionEmpty function
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. Available since version 5.19.0booleannull
flowIdnoFlow ID to get corresponding client settingsFlowId""

Liveness Results

typescript
type LivenessDetectionResult = {
  steps: Array<{
    name: string;
    image: {
      blob: Blob;
      encrypted?: {
        hex: string; // will be returned if outputEncryptionSettings.key is provided
      };
      id?: string; // upload image id
    };
  }>;
  // UI Only Mode: frontalFaces contains blobs of captured frontal face images
  // API Mode: frontalFaces contains objects with blob and id (id is returned from upload API)
  frontalFaces: Array<
    | Blob
    | {
        blob: Blob;
        id: string; // upload image id
      }
  >;
  frontalFacesEncrypted?: Array<{
    hex: string; // will be returned if outputEncryptionSettings.key is provided
  }>;
  frontalScaledImage?: Blob;
  capturedFrames: Array<{
    label: string;
    base64: string;
    metadata: string;
    index: number;
  }>;
  video?: Blob;
  apiCheckPassed?: boolean;
  verifyFaceLivenessResult?: any;
  verifyFacePortraitResult?: any;
  uploadFramesResult: Array<{ id: string }>;
};

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',
  },
},

Integration with Liveness Detection

Note: If frame capturing functionality is enabled, please refer to this section

  • See LivenessDetectionDoneResult shape here

Upload selfie images

  • You will need to upload selfie images returned by the SDK. Upload image API /v1/images
typescript
const onLivenessDetectionDone = ({ frontalFaces, steps }) => {
  const uploadFrontalImagePromises = frontalFaces.map((image: Blob) => {
    return API.uploadImage({
      file: image,
      label: "portrait",
      // Optional metadata field, you can add any custom info here
      metadata: JSON.stringify({
        label: "frontal",
      }),
    });
  });

  const uploadGestureImagePromises = steps.map((step) => {
    const blob = step.image.blob;
    return API.uploadImage({
      file: blob,
      label: "portrait",
      // Optional metadata field, you can add any custom info here
      metadata: JSON.stringify({
        label: step.name, // up, left, right gesture name
      }),
    });
  });

  const uploadFrontalImagesResult = await Promise.all(
    uploadFrontalImagePromises
  );
  const uploadGestureImagesResult = await Promise.all(
    uploadGestureImagePromises
  );
};

Call Verify Portrait Sanity API

  • Verify portrait sanity API /v1/verify_portrait_sanity_sync
  • Explanation of API parameters:
    • image: the LAST frontal face image ID in frontalFaces array returned by SDK.
typescript
API.verifyPortraitSanity({
  image: {
    id: <String>, // Image ID of last frontal image in 'frontalFaces' have been uploaded
  },
});

Call Verify Face Liveness API

  • Verify face liveness API /v1/verify_face_liveness_sync
  • Explanation of API parameters:
    • images: list of uploaded frontal face image IDs. The order MUST be the same as the frontalFaces array returned by SDK.
    • gesture_images: list of uploaded gesture image IDs with its step name.
    • videos: list of uploaded video file IDs if frame capturing functionality is enabled. Please refer to this section
typescript
API.verifyFaceLiveness({
  images: Array<{
    id: <String>, // Image ID returned by Upload API
  }>,
  gesture_images: Array<{
    gesture: <String>, // Step name
    images: [{ id: <String> }], // Image ID returned by Upload API
  }>,
  videos: [{ frames: capturedFrames }], // add this parameter if frame capturing functionality is enabled
});

Liveness Detection Full sample code

typescript
const onLivenessDetectionDone = ({ frontalFaces, steps, capturedFrames }) => {
  const uploadFrontalImagePromises = frontalFaces.map((image: Blob) => {
    return API.uploadImage({
      file: image,
      label: "portrait",
      // Optional metadata field, you can add any custom info here
      metadata: JSON.stringify({
        label: "frontal",
      }),
    });
  });

  const uploadGestureImagePromises = steps.map((step) => {
    const blob = step.image.blob;
    return API.uploadImage({
      file: blob,
      label: "portrait",
      // Optional metadata field, you can add any custom info here
      metadata: JSON.stringify({
        label: step.name, // up, left, right gesture name
      }),
    });
  });

  const uploadFrontalImagesResult = await Promise.all(
    uploadFrontalImagePromises
  );
  const uploadGestureImagesResult = await Promise.all(
    uploadGestureImagePromises
  );

  const frontalImageIds = uploadFrontalImagesResult.map((res) => ({
    id: res?.data?.image_id, // Sample response structure
  }));

  const gestureImageIds = uploadGestureImagesResult.map((res, index) => ({
    gesture: steps[index].name,
    images: [{ id: res?.data?.image_id }], // Sample response structure
  }));

  const verifyPortraitSanityResponse = await API.verifyPortraitSanity({
    image: {
      id: frontalImageIds[frontalImageIds.length - 1].id,
    },
  });

  const verifyLivenessResponse = await API.verifyFaceLiveness({
    images: frontalImageIds,
    gesture_images: gestureImageIds,

    // Add this parameter if frame capturing functionality is enabled
    // Please see more at "Selfie Frames Capturing" section
    videos: [{ frames: capturedFrames }],
  });
};

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

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
onFramesCapturednoThe callback when SDK return sequence by batch.functionEmpty function
onResetnoCallback when selfie flow is reset. For example, when timeout happens, this callback will be triggered. See more herefunctionEmpty 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);

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',
  fallbackUrls: ['FALLBACK URL 1', 'FALLBACK URL 2'] // optional
}
  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.

Integration Use cases

Different use cases may require different integration approaches. Below are some common use cases and their recommended integration methods.

Selfie Frames Capturing

  • If Frame capturing is enabled in Selfie flow (through clientSettings), SDK will provide onFramesCaptured callback to let client upload frames to server side through /v1/files API.
  • Terms:
    • Frame: each frame captured from camera stream during the liveness detection flow.
    • Batch of frames : SDK will capture frames in batch during the flow. Each batch contains multiple frames.

Sample code for onFramesCaptured:

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

const uploadBatchPromises = [];
// Callback when a batch of frames is captured
const onFramesCaptured = (frames: VideoFrame[]) => {
  // Convert frames array to binary file (Blob)
  const binaryFile = new Blob([JSON.stringify(frames)]);

  // Don't use `await` here to avoid blocking SDK flow
  // The `/v1/files` API will return file_id for the uploaded frames batch that we will use later
  const uploadPromise = API.uploadFrames({
    file: binaryFile,
    metadata: JSON.stringify({ type: "selfie" }),
    label: "video",
  });
  uploadBatchPromises.push(uploadPromise);
};

tv.livenessDetection({
  // ...
  onFramesCaptured: onFramesCaptured,
  // ...
});
  • Then the videos param in /v1/verify_face_liveness_sync 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
typescript
const onLivenessDetectionDone = async () => {
  // Here we await for all batches of frames to be uploaded
  const batchFileIds = await Promise.all(uploadBatchPromises).map(
    (response) => ({
      id: response?.data?.file_id, // We will get file_id from API.uploadFrames API response
    })
  );

  const verifyResult = await API.verifyFaceLiveness({
    // ... other params
    videos: batchFileIds,
  });
};

Selfie flow reset

  • When selfie flow is reset, either through timeout or user put their face out of detection zone, the frame capturing process should be reset too.
  • You can do that by clearing the uploadBatchPromises array in onReset callback.
typescript
const onReset = () => {
  uploadBatchPromises.length = 0;
  // Or
  // uploadBatchPromises = [];
};

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

Full sample code for frame capturing in selfie flow:

typescript
// Array holding all upload promises for each batch of frames
const uploadBatchPromises: Promise<any>[] = [];

// Callback when a batch of frames is captured
const onFramesCaptured = (frames: VideoFrame[]) => {
  // Convert frames array to binary file (Blob)
  const binaryFile = new Blob([JSON.stringify(frames)]);

  // Don't use `await` here to avoid blocking SDK flow
  // The `/v1/files` API will return file_id for the uploaded frames batch that we will use later
  const uploadPromise = API.uploadFrames({
    file: binaryFile,
    metadata: JSON.stringify({ type: "selfie" }),
    label: "video",
  });
  uploadBatchPromises.push(uploadPromise);
};

const onReset = () => {
  uploadBatchPromises.length = 0;
  // Or
  // uploadBatchPromises = [];
};

const onDone = async () => {
  // Here we await for all batches of frames to be uploaded
  const batchFileIds = await Promise.all(uploadBatchPromises).map(
    (response) => ({
      id: response?.data?.file_id, // We will get file_id from API.uploadFrames API response
    })
  );

  const verifyResult = await API.verifyFaceLiveness({
    // ... other params
    videos: batchFileIds,
  });
};

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

// Or
// tv.faceAuthentication({
//   // ...
//   onFramesCaptured: onFramesCaptured,
//   onReset: onReset,
//   onDone: onDone,
//   // ...
// });

Images/Frames 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.
  • You can generate the key in OutputEncryptionSettings using uuid library or any other method.

Images

  • The encrypted image in return via the encrypted field, as described in the SDK Results sections for Read ID Card 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.
  • Instead of uploading the image Blob returned by SDK, client will need to upload the encrypted hex string.
typescript
const binaryFile = new Blob([hexString]);
API.uploadImage(
  {
    file: binaryFile,
    // ... other params
  },
  {
    headers: {
      "X-TV-Key": outputEncryptionSettings.key,
    },
  }
);

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.
  • The parameters in /v1/files API remain the same, the only difference is the header X-TV-Key that need to be added.
typescript
const uploadPromise = API.uploadFrames(
  {
    // payload remains the same
  },
  {
    headers: {
      "X-TV-Key": outputEncryptionSettings.key, // Add this header
    },
  }
);

Hybrid mode with SDK

typescript
const serviceSettings = {
  enableUploadFrames: true,
  enableUploadImages: true,
  enableGetClientSettings: false,
  enableVerifySanityPortrait: false,
  enableVerifySanityIDCard: false,
  enableVerifyFaceLiveness: false,
  enableDetectIDCardTampering: false,
  enableReadIDCardInfo: false,
  enableRegisterFace: false,
  enableAuthenticateFace: false,
};
  • With the above configuration, SDK will only upload images/frames to server side, and client will handle the remaining API calls by themselves using the returned file ids from SDK.

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, please set allowsInlineMediaPlayback to true to allow inline video playback.

Zalo Miniapp configuration

  • When embedding eKYC SDK into Zalo Miniapp (ZMA), with the default configuration of ZMA, the UI of the SDK will occupy the whole screen of the device
  • In order to fix it, you must enable ZMA Status bar and Action bar either using configAppView or in app-config.json

configAppView

typescript
import { configAppView } from "zmp-sdk/apis";

configAppView({
  actionBar: {
    hide: false,
  },
  statusBarType: "normal",
});

app-config.json

json
{
  "app": {
    "statusBar": "normal",
    "actionBarHidden": false
  }
}

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

  • Only available if API Mode is enabled.
    • .readIDCardWithApiCall
    • .livenessDetection with apiCheck = true
    • .faceAuthentication with apiCheck = true
  • Type: object
Property
Description
TypeDefaultVersion
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 service.
Require enableUploadImages set to true
booleantrue5.19.0
enableVerifySanityIDCardEnable/Disable verify sanity for ID Card image service.
Require enableUploadImages set to true
booleantrue5.19.0
enableVerifyFaceLivenessEnable/Disable verify face liveness service.
Require enableUploadImages set to true
booleantrue5.19.0
enableDetectIDCardTamperingEnable/Disable detect ID Card tampering service.
Require enableUploadImages set to true
booleantrue5.19.0
enableReadIDCardInfoEnable/Disable read ID Card info service.
Require enableUploadImages set to true
booleantrue5.19.0
enableRegisterFaceEnable/Disable register face service.
Require enableUploadImages set to true
booleantrue5.19.0
enableAuthenticateFaceEnable/Disable authenticate face service.
Require enableUploadImages set to true
booleantrue5.19.0
  • Sample configuration:
typescript
const serviceSettings = {
  enableGetClientSettings: true,
  enableUploadFrames: true,
  enableUploadImages: true,
  enableVerifySanityPortrait: true,
  enableVerifySanityIDCard: true,
  enableVerifyFaceLiveness: true,
  enableDetectIDCardTampering: true,
  enableReadIDCardInfo: true,
  enableRegisterFace: true,
  enableAuthenticateFace: true,
};

ApiCredentials

Type: object

Keys to make API calls.

PropertyDescriptionTypeDefault
accessKeyAccess Keystring\"\"
secretKeySecret Keystring\"\"
apiUrlAPI base URLstring\"\"
fallbackUrlsFallback API base URLs, used when calls to apiUrl fail due to network errors, timeoutstring[]undefined

FlowId

Type: string

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

IDCardStep

Type: object

ValueDescriptionTypeRequired
scannerTypeScan mode"id_card" | "qr_code"yes
cardSideCard side to be scanned"front" | "back"no
titleStep titlestringno
descriptionStep descriptionstringno
cardTypeCard type to be scannedstringno

OutputEncryptionSettings

Type: object

PropertyDescriptionTypeDefault
keyEncryption key to encrypt output files (images/frames)string""

LanguageCode

Type: string

ValueDescriptionVersion
viVietnamese
enEnglish
es-mxMexican Spanish5.33.0
hiHindi5.35.0

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
});

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
Mexico (mx)es-mxes-mx

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 localeDefault es-mx 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.No se pudo abrir la cámara. Verifique la configuración de la cámara o inténtelo en otro navegador/dispositivo.
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.No se pudo abrir la cámara. Verifique la configuración de la cámara o inténtelo en otro navegador/dispositivo.
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.Estás usando el modo horizontal. Gira la pantalla al modo vertical y vuelve a intentarlo.
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 se puede cargar el modelo en este dispositivo/navegador. Prueba con otro.
no_permissionPlease grant the permission to access the camera.Vui lòng cấp quyền để truy cập camera.Conceda permiso a esta aplicación para acceder a la cámara.
no_faceFace not foundKhông tìm thấy khuôn mặtPor favor coloque su cara en el marco
partial_faceFace out of the boxKhông tìm thấy toàn bộ khuôn mặtPor favor, ajuste su cara completamente al marco
multiple_facesToo many facesQuá nhiều khuôn mặtMas de una cara detectada
face_too_smallFace too smallKhuôn mặt quá nhỏPor favor, acerque su rostro a la camara
face_too_largeFace too largeKhuôn mặt quá toPor favor, aleje su rostro de la camara
face_out_of_boxFace is out of boxKhuôn mặt không nằm trọn vẹn trong khung ảnhPor favor, ajuste su cara completamente al marco.
liveness_too_fastPlease turn your face SLOWLYVui lòng quay CHẬM lạiPor favor, gira la cabeza más lentamente
liveness_terminatedPlease try again.Vui lòng thực hiện lại.Por favor, intente de nuevo
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.Necesita girar su cara lentamente\nPor favor, intente de nuevo
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.Su cara no aparecio por mucho tiempo\nPor favor, intente de nuevo
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.Necesita completar el proceso entre <%= x %> segundos.\nPor favor, intente de nuevoMust 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ụpSostenga la identificación plana y mirando hacia la cámara
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)Mantenga el <%= side %> de su identificacion completamente dentro del marco.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)Por favor, acerque la identificación
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)Por favor, coloque la identificación completamente en el marco
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.Se detectó deslumbramiento.\nIntente inclinar la tarjeta o cambiar el ángulo
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ờPor favor, capture el frente de la identificación
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ấtPor favor, asegúrese de que solo haya una identificación dentro del marco
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ờPor favor, capture el reverso de la identificación
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ơnFoto borrosa, capture de nuevo para una mejor imagen
not_frontal_facePlease look straight to camera.Vui lòng nhìn thẳng vào camera.Por favor, mira directamente a la cámara
not_readableCamera already in use by another app/tab.Camera đang được sử dụng bởi ứng dụng/tab khác.Otra aplicación o pestaña del navegador ya está usando la cámara. Busque y cierre esa aplicación/pestaña.
close_eyeOpen your eyes clearlyVui lòng mở to mắtPor favor, abre bien los ojos
front_card_titleFront side of ID cardMặt trước CMND/CCCDFrente de la identificacion
back_card_titleBack side of ID cardMặt sau CMND/CCCDReverso de la identificacion
qr_titleScan QR code on ID cardQuét mã QR trên CCCDEscanear QR
front_card_descriptionPlease put the front side of ID card into the frameVui lòng đặt CMND mặt trước vào trong khungColoca tu identifiacion dentro del recuadro
back_card_descriptionPlease put the back side of ID card into the frameVui lòng đặt CMND mặt sau vào trong khungColoca tu identifiacion dentro del recuadro
btn_okOKTôi đã hiểuOK
frontfronttrướcfrente
backbacksaureverso
btn_use_this_pictureUse this pictureDùng ảnh nàyUse esta imagen
btn_try_againTry againThử lạiIntente de nuevo
confirm_popup_valid_qrQR code scannedĐã quét được mã QRCodigo QR escaneado
confirm_popup_invalid_qrCould not scan QR codeKhông quét được mã QRNo es posible escanear codigo QR
remaining_timeRemaining time: <%= x %> secondsThời gian còn lại: <%= x %> giâyRestante: <%= x %> segundosMust 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.Necesita tomar la foto dentro de los siguientes <%= x %> segundos.\nPor favor, intente de nuevoMust 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...Calentando, espere un momento
[TVWebSDK.Constants.FaceDirection.LEFT]Turn your face to the leftQuay mặt chậm qua tráiGira la cara a la izquierda
[TVWebSDK.Constants.FaceDirection.RIGHT]Turn your face to the rightQuay mặt chậm qua phảiGira la cara a la derecha
[TVWebSDK.Constants.FaceDirection.UP]Turn your face upNgước mặt chậm lên trênInclina la cabeza hacia arriba
[TVWebSDK.Constants.FaceDirection.FRONTAL]Look straight into the cameraNhìn thẳng cameraMira directamente a la cámara
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ãMueva la cámara a la posición del código QR en la identificcion. Alinee el código QR para que encaje en el marco de la cámara y escanearlo
qr_instructions_start_buttonScan QR codeEscanear código QR
qr_instructions_countdownStart in <%= x %> sBắt đầu trong <%= x %> giâyComienza en <%= x %> sMust 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 inválido! Escanee el código QR en su iD.
qr_invalid_popup_retry_buttonScan QR (on ID card)Quét lại QR (trên CCCD)Inténtelo de nuevo
qr_invalid_popup_skip_buttonSkipBỏ quaOmitir
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ã QRColoque el código QR de la ID dentro del marco
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.Tiempo de escaneo excedido. Inténtelo de nuevo.\nColoca la cámara sobre el código QR de la tarjeta. Alinea las cuatro esquinas e intenta mover la cámara (de lejos a cerca) para que la cámara del teléfono enfoque con mayor precisión.
session_timeout_popup_retry_buttonRetryThử lạiIntente de nuevo
session_timeout_popup_skip_buttonSkipBỏ quaOmitir

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

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

FAQs

1. Why am I getting license_invalid error?

This error happens when you haven't set up the license key correctly. Please make sure all of the following conditions are met:

  1. You have passed the parameter clientSettings correctly to the method. clientSettings should have this similar shape:
typescript
const clientSettings = {
  data: {
    settings: {
      sdk_settings: {
        // Your settings including license key
      },
    },
  },
};
  1. The domain you are using to run the SDK is added to the allowed domains list for that license key, including localhost. You can check the domain you are on using window.location.hostname.