Face Authentication
Authenticate
Checks a captured selfie against the customer’s registered face and returns a match verdict with a confidence score.
Request
Clients can provide image data to the TrustVision API by specifying the ID the image by using upload API, or by sending the image data as base64-encoded text.
| Name | Type | Required | Description |
|---|---|---|---|
cus_user_id | string | Required | User id of customer (max 256) |
faces | []image | Required | Images of the selfie (max 50). Any flash mode (flash, flash_8, flash_16, flash_32, flash_edge, flash_advanced) and passive_v2 need multiple images covering the captured frames — a single still image gives the model nothing to score. |
gesture_faces | []gesture_image | Optional | Array of gestures contain images (max 50) |
videos | []video | Optional | Array of videos selfie (max 50) |
gesture_videos | []gesture_video | Optional | Array of videos gesture (max 50) |
device_id | string | Optional | Device id of user (max 256) |
app_id | string | Optional | App id of customer (max 128) |
lat | float | Optional | Latitude of device |
lng | float | Optional | Longitude of device |
auth_type | string | Optional | Specify different authentication type (or different use-cases) (max 64). Selects the backend cluster setting for this request — controls which checks run (liveness, portrait sanity, face compare). Left unset, the full default FA pipeline runs every check. |
client_transaction_id | string | Optional | Client transaction ID |
selfie_type | string | Optional | Specify the characteristic of authentication type eg. Light, Standard, Edge, Advanced (max 64). Must be set explicitly to match the captured data — sending multiple faces images without setting this to the matching flash value (or passive_v2) means that liveness model never runs against them. |
history_return_count | int | Optional | Number of history authenticate records to return |
history_return_status | string | Optional | Status of history authenticate records to return (success, failure or all) |
metadata | json_object | Optional | Any key-value metadata to store with this login time |
transaction_data | json_object | Optional | Payment context for this authentication, read by the face authentication policy rules configured for your account. See Transaction data below. |
camera_challenge_performed | bool | Optional | Set automatically by the Mobile SDK: reports whether the camera challenge ran for this session. Report-only — it never affects processing or the result. Server-side callers that own the API call may set it themselves. |
- For parameter type
image, if you useid(resultidis returned from Upload Image API), omit parameterbase64,labelandmetadata. If you usebase64,labelandmetadata, omit parameterid. - For parameter type
video, if you useid(resultidis returned from Upload File API), omit parameterframes,metadata. If you useframes,metadata, omit parameterid.
The
image,gesture_image,video,FrameDataandgesture_videoshapes, and the gesture valuesleft,right,upanddown, are defined once on Common objects & errors.
{
"cus_user_id": string,
"client_transaction_id": string,
"faces": [
{
"id": string,
"base64": string,
"label": string,
"metadata": string_json,
}
],
"gesture_faces":[
{
"gesture": string,
"images": [
{
"id": string,
"base64": string,
"label": string,
"metadata": string_json,
},
{
"id": string,
"base64": string,
"label": string,
"metadata": string_json,
}
],
}
],
"videos": [
{
"id": string,
"metadata": string_json,
"frames": [
{
"base64": string,
"label": string,
"index": int,
"metadata": string_json,
},
{
"base64": string,
"label": string,
"index": int,
"metadata": string_json,
},
// other frames
],
},
{
"id": string,
"metadata": string_json,
"frames": [
{
"base64": string,
"label": string,
"index": int,
"metadata": string_json,
},
{
"base64": string,
"label": string,
"index": int,
"metadata": string_json,
},
// other frames
],
},
...
],
"gesture_videos": [
{
"gesture": string,
"videos": [
{
"id": string,
"metadata": string_json,
"frames": [
{
"base64": string,
"label": string,
"index": int,
"metadata": string_json,
},
{
"base64": string,
"label": string,
"index": int,
"metadata": string_json,
},
// other frames
],
},
... // other videos
]
},
... // other gesture videos
],
"device_id": string,
"app_id": string,
"lat": float,
"lng": float,
"auth_type": string,
"selfie_type": string,
"history_return_count": int,
"history_return_status": string,
"metadata": json_object,
"transaction_data": json_object,
"camera_challenge_performed": bool
}
camera_challenge_performedis only meaningful whenselfie_typeis a flash variant orpassive_v2— for other flows it is alwaysfalse. A malformed (non-boolean) value is ignored with a server-side warning and the request still processes normally; the value is stored with the authentication history.
The selfie_type parameter
selfie_type picks the liveness technique the call runs, which is what separates the Light, Standard, Edge and Advanced tiers named in the parameter table.
selfie_type | Description |
|---|---|
light | Light Authen |
passive | Standard Authen |
passive_v2 | Standard Authen (v2) |
active | Advanced Authen use active liveness (gestures) |
flash | Advanced Authen uses full flash liveness |
flash_8 | Edge Authen uses flash liveness with 8 frames |
flash_16 | Edge Authen uses flash liveness with 16 frames |
flash_32 | Advanced Authen uses flash liveness with 32 frames |
flash_edge | Edge Authen uses flash liveness with minimum time |
flash_advanced | Advanced Authen uses full flash liveness |
The auth_type parameter
auth_type labels the use-case this authentication belongs to; the values below are the common ones, and you can define any authentication type based on your business. Each auth_type maps to a backend cluster setting that decides which checks run for the request — liveness, portrait sanity, face compare — so it is how you scope a request to fewer checks than the full default pipeline; leaving it unset runs every check.
auth_type | Description |
|---|---|
reset_password | Authenticate to reset password flow |
login | Use face to replace text password |
payment | Authenticate payment |
transfer | Authenticate transfer |
transfer_type_A | Authenticate transfer type A |
transfer_type_B | Authenticate transfer type B |
transfer_type_C | Authenticate transfer type C |
transfer_type_D | Authenticate transfer type D |
... | You can define any authentication type based on your business |
Sample request
curl -X POST \
https://tv-staging.trustingsocial.com/api/v1/face_auth \
-H 'Authorization: TV <YOUR ACCESS KEY>:<CREATED SIGNATURE>' \
-H 'X-TV-Timestamp: 2019-04-21T18:00:15+07:00' \
-H 'Content-Type: application/json' \
-d \
'
{
"cus_user_id": "3b4bf7b6-088b-4931-9279-9259f5c34fe3",
"faces": [
{
"id": "a4facba3-334b-41fb-97e3-4766cb70ae29"
},
{
"id": "c5a2a405-f152-4248-b746-3af76b241ef7"
},
{
"id": "5d0345f7-da7c-44e8-bd6c-a8c08c806227"
}
],
"gesture_faces": [
{
"gesture": "left",
"images": [
{
"id": "d849a3ce-d209-4605-99a7-3d369234fd5e"
}
]
},
{
"gesture": "up",
"images": [
{
"id": "551fc06a-14fd-44dc-8838-abe895007c36"
}
]
},
{
"gesture": "right",
"images": [
{
"id": "536d2457-bfc0-4249-85f2-8c4053bdec58"
}
]
}
],
"videos": [
{
"id": "61801fcd-b109-49f7-b871-ae82c2a666cc",
},
{
"id": "1f93d229-7f1c-44f9-a85b-1be4c2d4be41",
}
],
"metadata": {
"key1": {
"key11": "value11",
"key12": "value12"
},
"key2": "value2"
}
}
'
A Flash mode request sends multiple faces — one per captured frame — and sets selfie_type to the matching flash value, instead of the single image a passive request would use:
curl -X POST \
https://tv-staging.trustingsocial.com/api/v1/face_auth \
-H 'Authorization: TV <YOUR ACCESS KEY>:<CREATED SIGNATURE>' \
-H 'X-TV-Timestamp: 2019-04-21T18:00:15+07:00' \
-H 'Content-Type: application/json' \
-d \
'
{
"cus_user_id": "3b4bf7b6-088b-4931-9279-9259f5c34fe3",
"selfie_type": "flash_16",
"auth_type": "login",
"faces": [
{
"id": "b635e407-13e4-403b-8731-80821cfa4922"
},
{
"id": "1cb8712c-56da-4c26-a850-08618248a370"
}
]
}
'
Response
data.auth_check carries the verdict — a result of matched or unmatched, and a score for how likely it is that the two faces match (0-1). A 200 only means the request finished processing: whether the authentication passed is in data.status, which is "success" or "failure", and in auth_check — never in the HTTP status code.
Each entry under histories is a history record, and errors uses the shared error envelope; both are documented once on Common objects & errors.
{
"data": {
"status": string, // "success" or "failure"
"auth_check": {
"score": float, // how likely that face1 and face2 are matched (0-1)
"result": string // "matched", "unmatched"
},
"request_id": string, // return request id to the client and can use request-id to check logs.
"user_metadata": json_object, // return user metadata
"validate_faces_result": {
"sanity_verdict": string, // portrait_sanity check result, if field not exist or value is "", it means portrait_sanity check is not performed
"is_live": bool, // liveness check result, if field not exist or value is null, it means liveness check is not performed
"sanity_check: {...}, // full sanity check response
"liveness_check: {...} // full liveness check response
},
"auth_id": string, // id of this authentication request
"histories": [
{
"id": string, // id of this history
"client_id": string, // client id
"cus_user_id": string, // user id
"face_auth_id": string, // id of selfie image containing user's face
"id_card_face_id": string, // id of id card image containing user's face
"live_face_id": string, // id of input image for this login request
"device_id": string, // device id input for this login request
"app_id": string, // app id input for this login request
"latitude": float, // latitude input for this login request
"longitude": float, // longitude input for this login request
"created_at": string, // time of this login request
"auth_action": string, // "login"
"status": string, // "success" or "failure"
"result": string, // detail result of this login request
"metadata": json_object, // metadata input for this login request
"face_type": string, // type of face used to compare against the input face to authenticate ("selfie", "id_card")
},
... // other history
]
},
"errors": [
{
"code": string,
"message": string,
"detail": {
"field": string, // optional, which parameter is invalid.
... // any other information that can be useful for client
},
},
... // other errors
]
}
The expiry_date of selfie face will be returned in
user_metadatafield asexpiry_date_facein RFC3339 format.
A matched authentication, with two history records, comes back like this:
{
"data": {
"status": "success",
"auth_check": {
"score": 0.9999999862517992,
"result": "matched"
},
"request_id": "cfc7b8d5-f2e2-437b-80e5-d600dfad26e2",
"user_metadata": {
"key1": "value1",
"key2": 2,
"expiry_date_face": "2023-12-27T03:33:06.307977Z",
},
"validate_faces_result": {
"sanity_verdict": "good",
"is_live": true,
"sanity_check": {
"portrait_sanity": {
"score": 1,
"verdict": "good"
},
"request_id": "9f89377f-4bb4-4ecb-884c-f46f1d7d6099",
"status": "success"
},
"liveness_check": {
"images": null,
"is_live": true,
"request_id": "baee3d03-8af4-421b-8968-a0f6172b4df8",
"score": 1,
"status": "success"
}
},
"auth_id": "d4f99f9b-4862-437f-a9c9-608e050d06c6",
"histories": [
{
"id": "f1ca6795-cbd4-4eec-821f-b4ac646db627",
"client_id": "0ef0f7e9-7f2f-4767-84b8-f76da7701c46",
"cus_user_id": "3b4bf7b6-088b-4931-9279-9259f5c34fe3",
"face_auth_id": "887249dc-681c-4231-aff9-67734833aca7",
"id_card_face_id": "4f959f4a-f5de-4b50-8724-224e709d06c2",
"live_face_id": "009161c9-9c89-4573-8119-8f513de4449c",
"created_at": "2023-12-27T03:33:06.307977Z",
"auth_action": "login",
"status": "success",
"result": "",
"face_type": "selfie"
},
{
"id": "8c23af95-aaa0-4448-bfba-68acce31236e",
"client_id": "0ef0f7e9-7f2f-4767-84b8-f76da7701c46",
"cus_user_id": "3b4bf7b6-088b-4931-9279-9259f5c34fe3",
"face_auth_id": "",
"id_card_face_id": "4f959f4a-f5de-4b50-8724-224e709d06c2",
"live_face_id": "009161c9-9c89-4573-8119-8f513de4449c",
"created_at": "2023-12-21T07:24:19.829484Z",
"auth_action": "login",
"status": "failure",
"result": "image is not liveness",
"face_type": "id_card"
}
]
}
}
A Flash mode authentication (selfie_type: "flash_16") carries the same verdict shape, with validate_faces_result.liveness_check scoring every faces image individually — the same shape Verify face liveness returns:
{
"data": {
"status": "success",
"auth_check": {
"score": 0.910237543284893,
"result": "matched"
},
"request_id": "3f2a9b3e-1c9d-4a5e-9b7a-2f6d9a1b7e3c",
"validate_faces_result": {
"sanity_verdict": "good",
"is_live": true,
"sanity_check": {
"portrait_sanity": {
"score": 1,
"verdict": "good"
},
"status": "success"
},
"liveness_check": {
"images": [
{
"id": "1cb8712c-56da-4c26-a850-08618248a370",
"is_live": true,
"score": 0.910237543284893
},
{
"id": "b635e407-13e4-403b-8731-80821cfa4922",
"is_live": true,
"score": 0.9999598160466121
}
],
"is_live": true,
"score": 0.910237543284893,
"status": "success"
}
},
"auth_id": "8c8e6a2b-2f9d-4a5e-9b7a-2f6d9a1b7e3c",
"histories": []
}
}
Failure codes
If the data.status is "failure", the "errors" field will tell you why it failed.
| Error code | Description |
|---|---|
image_too_blur | the input image is too blur |
image_too_dark | the input image is too dark |
image_too_bright | the input image is too bright (glare) |
image_has_no_faces | the input image has no faces |
image_non_liveness | the input image is not liveness |
face_auth_user_deactivated | the user is deactivated |
face_auth_user_unregistered | the user is unregistered from face_auth service |
face_auth_user_locked | the user is locked from face_auth service |
Transport, auth and rate-limit errors are the same on every call — see Common objects & errors.
Transaction data
transaction_data carries the payment context behind an authentication. It is optional and purely additive: omitting it leaves the request, and the face-match verdict, exactly as they are today. What it adds is context for the face authentication policy rules configured for your account — those rules can read every field below, so a policy can, for example, ask for a step-up or a manual review when a large transfer goes to a beneficiary that was added minutes ago.
Two things to know about scope:
- The object is read only when face authentication policy checks are enabled for your account. With those checks off it is accepted and ignored.
- It is meaningful only on this endpoint. Register a face accepts the field for symmetry but does not evaluate or store it.
Every field is optional — send the ones you have. Fields marked encrypted at rest are stored encrypted by the server: send them in plaintext, and policy rules still evaluate them in plaintext.
Transaction core
| Field | Type | ISO 20022 path | Description |
|---|---|---|---|
transaction_id | string | PmtId/EndToEndId | Your identifier for the transaction |
transaction_timestamp | string | GrpHdr/CreDtTm | When the transaction was created, as an ISO 8601 timestamp |
transaction_amount | number | IntrBkSttlmAmt | Settlement amount |
currency_code | string | IntrBkSttlmAmt/@Ccy | ISO 4217 currency code, e.g. VND |
transaction_type | string | PmtTpInf | Coarse transaction type, e.g. transfer |
channel | string | — | Channel the transaction was initiated from, e.g. mobile_app |
transaction_status | string | pacs.002 TxSts | Status of the transaction |
Parties & accounts
| Field | Type | ISO 20022 path | Description |
|---|---|---|---|
account_number | string | DbtrAcct/Id | Sender account number — encrypted at rest |
account_holder_name | string | Dbtr/Nm | Sender account holder name — encrypted at rest |
bank_code | string | DbtrAgt/FinInstnId/(BICFI or ClrSysMmbId) | Sender bank code — encrypted at rest |
beneficiary_account_number | string | CdtrAcct/Id | Recipient account number — encrypted at rest |
beneficiary_account_holder_name | string | Cdtr/Nm | Recipient account holder name — encrypted at rest |
beneficiary_bank_code | string | CdtrAgt/FinInstnId/(BICFI or ClrSysMmbId) | Recipient bank code — encrypted at rest |
Risk & payment context
| Field | Type | ISO 20022 path | Description |
|---|---|---|---|
is_new_beneficiary | bool | — | Whether this is the first payment to this beneficiary |
days_since_beneficiary_added | int | — | Days since the beneficiary was added to the sender's payee list |
beneficiary_known_mule_flag | bool | — | Whether the beneficiary is on your known-mule list |
beneficiary_name_match_result | string | — | Outcome of the beneficiary name check (e.g. NAPAS name-check) |
beneficiary_proxy_alias | string | CdtrAcct/Prxy/Id | Beneficiary proxy alias, e.g. phone or VietQR alias — encrypted at rest |
beneficiary_country | string | Cdtr/PstlAdr/Ctry | Beneficiary country |
beneficiary_bank_country | string | CdtrAgt/FinInstnId/PstlAdr/Ctry | Beneficiary bank country |
beneficiary_account_open_date | string | — | When the beneficiary account was opened |
relationship_to_beneficiary | string | — | Declared relationship between sender and beneficiary |
direction | string | CdtDbtInd | Credit or debit indicator (CRDT / DBIT) |
purpose_code | string | Purp/Cd | Payment purpose code |
transaction_category | string | PmtTpInf/CtgyPurp/Cd | Category purpose code |
transaction_remark | string | RmtInf/Ustrd | Unstructured remittance information / transfer note |
payment_rail | string | PmtTpInf/LclInstrm/Cd | Payment rail or local instrument |
initiation_method | string | — | How the payment was initiated |
merchant_category_code | string | MrchntCtgyCd | Merchant category code |
original_amount | number | InstdAmt | Instructed amount, before any conversion or fees |
napas_system_trace_ref | string | PmtId/ClrSysRef | Clearing-system reference |
global_tx_uuid | string | PmtId/UETR | End-to-end unique transaction reference (UETR) |
is_recurring | bool | — | Whether the payment is part of a recurring series |
sender_country | string | Dbtr/PstlAdr/Ctry | Sender country |
sender_account_open_date | string | — | When the sender account was opened |
sender_date_of_birth | string | Dbtr/Id/PrvtId/DtAndPlcOfBirth/BirthDt | Sender date of birth — encrypted at rest |
customer_risk_level | string | — | Your own risk rating for the customer |
balance_before | number | — | Account balance before the transaction |
balance_after | number | — | Account balance after the transaction |
time_since_last_inflow | int | — | Seconds since the last credit into the account |
velocity_features | json_object | — | Free-form velocity features you compute for the customer |
txn_velocity_window | json_object | — | Free-form transaction counts/amounts over your own time windows |
failed_auth_attempts | int | — | Failed authentication attempts leading up to this transaction |
days_since_credential_change | int | — | Days since the customer last changed a credential |
sim_swap_recent_flag | bool | — | Whether a recent SIM swap was detected for the customer |
authentication_method | string | — | Authentication method used for the payment itself |
biometric_required_flag | bool | — | Whether your own policy already requires biometrics for this transaction |
ip_address | string | — | IP address the transaction was initiated from |
Field naming: canonical names and ISO 20022 paths
Each field can be keyed by either its canonical name (the Field column above) or the ISO 20022 path shown next to it; both are normalized to the canonical name on ingest. If the same logical field arrives under both keys in one request, the ISO-keyed value wins.
- Canonical names are matched case-insensitively, so
channelandChannelboth bind. ISO paths must match exactly as written above. Acct/OpngDtandBal/Amtare deliberately not accepted as aliases — each maps to two canonical fields (sender vs. beneficiary open date, balance before vs. after) and cannot be told apart from the key alone. Use the canonical names for those four fields.- Keys that are not listed above are kept verbatim and stay readable by policy rules, so bank-specific fields can be sent alongside the standard ones.
Example
{
"cus_user_id": "customer-001",
"auth_type": "transfer",
"selfie_type": "passive",
"faces": [{ "id": "5f0e9dbe-1e0f-4bd7-9e39-5f0b8ba4c9d1" }],
"transaction_data": {
"transaction_id": "txn-001",
"transaction_timestamp": "2024-01-15T10:30:00Z",
"transaction_amount": 5000000,
"currency_code": "VND",
"transaction_type": "transfer",
"channel": "mobile_app",
"account_number": "1234567890",
"beneficiary_account_number": "0987654321",
"beneficiary_bank_code": "970436",
"is_new_beneficiary": true,
"days_since_beneficiary_added": 0,
"direction": "DBIT",
"balance_after": 1200000
}
}