eKYC Platform Integration Guidelines
Prerequisites
Our client will be provided following credentials to initialize a unique token for an user flow:
I. Generate token to send to eKYC Platform
Reference Authentication mechanism
II. Integrate with your platform
Two integration modes are available. Choose based on your use case, then follow the guide for your platform.
flow_id selects which eKYC journey runs for a given session — passed as a query/body parameter to Authentication, the Platform API, or the Vision Score API. Built-in flows are provisioned out of the box; flexible/custom flows are provisioned per client by TrustVision — contact TrustVision to receive your custom flow_id before integrating.
Full journey managed by TrustVision. Low-code. Built-in. No provisioning needed.
| flow_id | Description |
|---|---|
id_selfie | Built-in flow: ID → Selfie → Matching. |
selfie_id | Built-in flow: Selfie → ID → Matching. |
id_nfc_selfie | Built-in flow: ID capture → NFC chip read → Selfie → Matching. |
qr_nfc_selfie | Built-in flow: QR code scan → NFC chip read → Selfie → Matching. |
id_form_selfie | Built-in flow: ID capture → user-filled form → Selfie → Matching. |
vision_score_sanity_liveness | Called via the Vision Score API. Runs Vision Score with passive liveness: 1–3 selfie images, selfie_type: "passive", with optional liveness video frames. |
vision_score_only | Called via the Vision Score API. Runs Vision Score against a single selfie image, with no liveness check. This is also the default flow_id when none is supplied to Vision Score. |
💡 Flexible flows — Contact TrustVision to receive your custom
flow_idbefore integrating. Once you have it, use it exactly the same way as the built-inflow_idvalues shown above.
II.1 Web or WebView
Use EncodedBase64Token generated from step I and concatenate to provided URL
Final URL:
| Region | Staging URL |
|---|---|
| Philippines | https://ekyc-platform-ph-staging.trustingsocial.com/lu/[EncodedBase64Token] |
| Vietnam | https://ekyc-platform-vn-staging.trustingsocial.com/lu/[EncodedBase64Token] |
| India | https://ekyc-platform-in-staging.trustingsocial.com/lu/[EncodedBase64Token] |
| Mexico | https://ekyc-platform-mx-staging.trustingsocial.com/lu/[EncodedBase64Token] |
Optional query parameters
You can append the following optional query parameters to the URL to control which eKYC journey runs, the allowed ID card types, and the UI language. Every parameter is optional — omit one to fall back to the default configured for your token.
| Parameter | Type | Required | Description |
|---|---|---|---|
flow_id | string | No | Selects which eKYC journey to run — see the flow_id tables under II. Integrate with your platform above. If omitted, the default flow configured for your token is used. |
card_types | string | No | Comma-separated list of allowed ID card types, e.g. vn.national_id,vn.passport. Restricts which ID documents the user can submit. If omitted, all card types configured for your account are allowed. |
language | string | No | UI language for the eKYC journey. One of vi, en, or es-mx. If omitted, the lender's default language is used. |
Note: The
languagequery parameter corresponds to thelangparameter of the Platform Initialization API.
Examples
Built-in flow (ID → Selfie) with Vietnamese UI:
https://ekyc-platform-vn-staging.trustingsocial.com/lu/[EncodedBase64Token]?flow_id=id_selfie&language=vi
Flexible flow (ID only), restricted to the national ID card, with English UI:
https://ekyc-platform-vn-staging.trustingsocial.com/lu/[EncodedBase64Token]?flow_id=[YOUR_ID_ONLY_FLOW_ID]&card_types=vn.national_id&language=en
Replace [YOUR_ID_ONLY_FLOW_ID] with the flow ID provisioned for you by TrustVision.
II.2 Android
II.2.1 eKYC Platform
Refer to this for more details.
II.2.2 eKYC Platform Webview
Refer to this for more details.
II.3 iOS
II.3.1 eKYC Platform
Refer to this for more details.
II.3.2 eKYC Platform Webview
Refer to this for more details.
II.4 Flutter
II.4.1 eKYC Platform
Refer to this for more details.
II.4.2 eKYC Platform Webview
Refer to this for more details.
II.5 React Native
II.5.1 eKYC Platform
Refer to this for more details.
II.5.2 eKYC Platform Webview
Refer to this for more details.
III. How client will get the result
III.1 Use webhook URL
- Use-case: TrustingSocial's Backend call to Client's Backend.
- The result will be sent to the client's webhook URL in JSON format. This is the Webhook decisions URL field in the channel's Technical configuration; it is not the Callback URL field, which is the redirect URL.
- The client should verify the signature that was signed using the TS RSA key.
Sample result:
View result by flow type: Full Flow | ID Only
{
"verdict": "pending|approve|review|reject|abandoned",
"signature": "ImFwcGxpY2F0aW9uX3VuaXF1ZV90b2tlbvN4A6zZUr3SAg7mH6vUiypRRhbCsyoVU9E2/ZFQywpPTcQ2tUC3i66jy85yjwL4XDiaEgQBJh9ZzOQAl8vKbo2cH/rVkOJYQR8HLOE5yeBhHFk9xKVfMyywtXwpaHkMH+U7AWUqPvyYnRMfHC3Pgx/V3npDvDrRSMpRLe9pAKw+1GSrGnw+wY7eTHVWeo6ilQnJ3r8nSI4yZGxnoUpffQenW8tPYuRYJuODUiXochsm408oSIzyZCvyPWkTCso93P2xnEshgMbHkqyTGUeOoePdJT9Z3OolbZbkqyEbQ==",
"application_id": 10,
"application_unique_token": "ad083945-9620-486c-be7d-c3f5dab870a1",
"application_user_id": "01234567889",
"file_ids": {
"id_front_img_uuid": "8fdcb17e-5d41-4814-8d4f-8c1bfb51df20",
"id_back_img_uuid": "93cef1cd-2782-4264-9c65-cce78ce3f549",
"qr1_img_uuid": ""
},
"ekyc_ocr": {
"card_info": {
"card_info": [
{ "confidence_score": 0, "confidence_verdict": "", "field": "crn", "value": "0123456789" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "last_name", "value": "SANTOS" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "first_name", "value": "JOSE" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "middle_name", "value": "CRUZ" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "date_of_birth", "value": "1990/01/01" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "gender", "value": "F" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "address", "value": "CABAROAN SAN ESTEBAN ILOCOS SUR PHL 2706" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "id", "value": "0123456789" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "card_type", "value": "ph.ump" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "card_label", "value": "ph.ump.front" },
{ "confidence_score": 0, "confidence_verdict": "", "field": "name", "value": "JOSE CRUZ SANTOS" }
],
"converted_card_info": {
"address": "CABAROAN SAN ESTEBAN ILOCOS SUR PHL 2706",
"birth_date": "1990/01/01",
"card_label": "ph.ump.front",
"card_type": "ph.ump",
"gender": "F",
"id_number": "0123456789",
"name": "JOSE CRUZ SANTOS"
}
},
"process_status": "success",
"processed_at": "2024-09-26T18:11:29+07:00",
"sanity_check_result": "good",
"sanity_check_score": 1,
"sanity_check_status": "success",
"search_face_result": null,
"search_face_status": "disabled"
},
"id_tampering": {
"score": 1,
"verdict": "good"
},
"form_data": {
"contactInfo": {
"familyAddress": "CABAROAN SAN ESTEBAN ILOCOS SUR PHL 2706"
},
"personalInfo": {
"birthday": "1990/01/01",
"fullName": "JOSE CRUZ SANTOS",
"gender": "",
"idCard": "0123456789"
}
},
"time": "2024-09-27T09:50:32+07:00"
}
Explanation of the fields:
| Field name | Datatype | Description |
|---|---|---|
| application_id | int | Application ID returned by TV, can be used to get application detail later |
| application_unique_token | string | the unique token for User Session, generated after decrypting the client's encrypted token |
| application_user_id | string | the decrypted client-generated user ID |
| file_ids | object | Contains images, frame video, merged video. |
| ekyc_face | object | Face image processing results including: sanity check, liveness check, comparison (with ID card face), searching |
| ekyc_ocr | object | ID Card image processing result including: sanity check, card info, search faces |
| form_data | object | The user-filled form |
| id_tampering | object | ID card tampering check |
| verdict | string | Reference |
| time | string | The time of the message |
| All the score fields | float/decimal | Decimal number between 0 and 1 inclusively |
Reading failures in the payload
The payload has no error-code field and no errors array — it reports the outcome of the journey, not of an API call. A check that did not pass shows up in verdict and in the status fields inside ekyc_ocr, ekyc_face and id_tampering:
- Verify the signature.
- Read
verdict—pending,approve,review,rejectorabandoned. - Read the status fields of each check object present in the payload —
process_status,sanity_check_status,liveness_status,compare_status,search_face_status— whose values are listed under Check status fields.
Error codes such as record_not_found or expired_token belong to the API calls you make yourself, not to the webhook. Where each one is listed is described under The application result. To see where a reason for a failed check can be found, read Finding the reason for a failure.
When the webhook is sent
- The webhook is sent when the application reaches a final state,
successorfailure, and also when it enters the e-sign step (esign.intro), and only if a Webhook decisions URL is configured on the channel. Without one, nothing is sent. - A system error or a timeout inside a check does not send a webhook. The applicant stays on that step and can retry. If they exceed the retry limit and your account is configured to reject, the application moves to
failureand a webhook is sent withverdict: "reject"and no reason. abandonedis sent only when expiry processing is enabled for your account. It is a scheduled job that reports applications that expired without finishing.- If a delivery fails, TrustVision attempts delivery up to 5 times (the first attempt plus retries; the limit can differ per deployment) and then stops. Nothing is queued after that, so if your endpoint was down, retrieve the result with Application detail.
How to verify signature from JSON data:
- Public key:
Philippines environment:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwC4C2c+nbI5GN3RseYUE
6xB1SObAk1HNFrs41sbBL1ZgMcYmXnuPrqXehzdqbBSYnWwFufLwFnFfAox7dInw
2olB8QCX9aOYXxcCX+T4JKJxYMuMC7+vuKVip22U+6WUTOaHqGAB2ZmsGPp7dpN4
Q+5duwiYam4izHwiFZ2bGmXbDqARtRlO+UnA8JMVE9LISQD1p238Id+AY3K9W8GQ
W3luLJttKZ0/ulJ54JsVxg5wF3EtohfcvVSJD/6rY0BLUINB5o9nLuGYkjpd+7V0
oB+o4YDMPMOTu8IJa0tbZg8HClr+/Rq3W4xle/NFS1YbLmp45MJ8APsbX9OkG+jM
JwIDAQAB
-----END PUBLIC KEY-----
- To verify the received data, follow these steps:
- Extract the signature: Isolate the value of the signature field from the received JSON data.
- Prepare the data payload: Create a representation of the JSON data excluding the signature field. This is the original data that was signed.
- Verify the signature: Using the public key, verify the extracted signature (from step 1) against the prepared data payload (from step 2).
Sample code:
The signature covers a canonical encoding of the payload: object keys sorted alphabetically at every level, no whitespace, and <, > and & written as the escapes \u003c, \u003e and \u0026. Re-serialising with any other settings changes the bytes, so the digest changes and verification fails even though the signature is genuine. Go's encoding/json produces this form by default; the Java sample configures Jackson to match it.
package com.trustingsocial.example;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class VerifySignature {
public static void main(String[] args) throws Exception {
String input = "YOUR_DATA";
String publicKeyPem =
"-----BEGIN PUBLIC KEY-----\n"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwC4C2c+nbI5GN3RseYUE\n"
+ "6xB1SObAk1HNFrs41sbBL1ZgMcYmXnuPrqXehzdqbBSYnWwFufLwFnFfAox7dInw\n"
+ "2olB8QCX9aOYXxcCX+T4JKJxYMuMC7+vuKVip22U+6WUTOaHqGAB2ZmsGPp7dpN4\n"
+ "Q+5duwiYam4izHwiFZ2bGmXbDqARtRlO+UnA8JMVE9LISQD1p238Id+AY3K9W8GQ\n"
+ "W3luLJttKZ0/ulJ54JsVxg5wF3EtohfcvVSJD/6rY0BLUINB5o9nLuGYkjpd+7V0\n"
+ "oB+o4YDMPMOTu8IJa0tbZg8HClr+/Rq3W4xle/NFS1YbLmp45MJ8APsbX9OkG+jM\n"
+ "JwIDAQAB\n"
+ "-----END PUBLIC KEY-----";
ObjectMapper mapper = canonicalJsonMapper();
// Extract signature and prepare data for verification
Map<String, Object> rawData =
mapper.readValue(input, new TypeReference<Map<String, Object>>() {});
String signature = (String) rawData.remove("signature");
String data = mapper.writeValueAsString(rawData);
// Load the RSA public key PEM string (be careful with the \n, space, tab) or from file
PublicKey publicKey = loadRSAPublicKeyString(publicKeyPem);
// PublicKey publicKey = loadRSAPublicKey("key.pub");
// Verify the signature
if (verify(publicKey, data, signature)) {
System.out.println("Signature is valid!");
} else {
System.out.println("Error verifying signature: signature does not match");
}
}
/*
* The signature was produced over the canonical encoding of the payload: object keys sorted
* alphabetically at every level, <, > and & escaped rather than emitted raw, and every number
* spelled exactly as it arrived. Jackson does none of these by default, and any difference in
* the bytes changes the SHA-1 digest, so this configuration is what makes the re-encoded
* payload byte-identical to the one that was signed.
*/
private static ObjectMapper canonicalJsonMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
// Read decimals as BigDecimal, which keeps their exact digits. Parsing them into a double
// instead reformats on the way out — a score of 0.0005 comes back as 5.0E-4 — and the
// signature then fails on a payload that is perfectly genuine.
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
mapper.registerModule(exponentCaseModule());
mapper.getFactory().setCharacterEscapes(new HtmlCharacterEscapes());
return mapper;
}
/** BigDecimal prints an upper-case exponent (1E-7); the signed form uses lower case (1e-7). */
private static SimpleModule exponentCaseModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(
BigDecimal.class,
new JsonSerializer<BigDecimal>() {
@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeNumber(value.toString().replace('E', 'e'));
}
});
return module;
}
private static class HtmlCharacterEscapes extends CharacterEscapes {
private static final long serialVersionUID = 1L;
private final int[] escapes = CharacterEscapes.standardAsciiEscapesForJSON();
HtmlCharacterEscapes() {
escapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
escapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
escapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;
}
@Override
public int[] getEscapeCodesForAscii() {
return escapes;
}
@Override
public SerializableString getEscapeSequence(int ch) {
switch (ch) {
case '<':
return new SerializedString("\\u003c");
case '>':
return new SerializedString("\\u003e");
case '&':
return new SerializedString("\\u0026");
default:
return null;
}
}
}
private static PublicKey loadRSAPublicKey(String filePath) throws Exception {
return loadRSAPublicKeyString(
new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8));
}
private static PublicKey loadRSAPublicKeyString(String pem) throws Exception {
String base64 =
pem.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(base64));
return KeyFactory.getInstance("RSA").generatePublic(spec);
}
/** RSASSA-PKCS1-v1_5 over a SHA-1 digest — the scheme the signature was produced with. */
private static boolean verify(PublicKey publicKey, String msg, String signature)
throws Exception {
Signature verifier = Signature.getInstance("SHA1withRSA");
verifier.initVerify(publicKey);
verifier.update(msg.getBytes(StandardCharsets.UTF_8));
return verifier.verify(Base64.getDecoder().decode(signature));
}
}
III.2 Call HTTP Request
- Use-case: Client's Backend call to TrustingSocial's Backend.
- The client use the API Application Detail to get the user flow result.
III.3 Redirect URL
- Use-case: TrustingSocial's Website redirect to Client's Website.
- After the user finishes, they can be redirected to a URL you provide. Trust Central labels this field Callback URL, but it only redirects the user's browser; it does not receive the result (that is the webhook URL). Your backend can then call our API to retrieve the results. See details III.2
- The system automatically appends
application_idandapplication_user_idas query parameters to the redirect URL when performing the redirect.