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.
Full journey managed by TrustVision. Low-code.
Example flow_id values:
id_selfie
selfie_id
id_nfc_selfie
qr_nfc_selfie
id_form_selfie
vision_score_sanity_liveness
vision_score_only
… and more
Built-in. No provisioning needed.
💡 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 below.
II.1 Web or WebView
Use EncodedBase64Token generated from step I and concatenate to provided URL
Final URL:
- PH: https://ekyc-platform-ph-staging.trustingsocial.com/lu/[EncodedBase64Token]
- VN: https://ekyc-platform-vn-staging.trustingsocial.com/lu/[EncodedBase64Token]
- IN: https://ekyc-platform-in-staging.trustingsocial.com/lu/[EncodedBase64Token]
- MX: 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. Built-in flows include id_selfie (ID → Selfie → Matching) and selfie_id (Selfie → ID → Matching). Flexible/custom flows — such as ID-only, Selfie-only, or ID → Form → Selfie — are also supported. The exact flow_id for each flow is provisioned per client by TrustVision. 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 callback URL
- Use-case: TrustingSocial's Backend call to Client's Backend.
- The result will be sent to the client's callback URL in JSON format.
- 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",
"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 |
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. Your backend can then call our API to retrieve the results. See details III.2