Skip to content

eKYC Overview ​

eKYC is CAKE's hosted identity-verification platform: a set of stateless HTTP APIs that upload an image once, then run ID card checks (detection, sanity, OCR, tampering) and face checks (sanity, liveness, comparison, indexing and search) against that image. This page covers authentication, the shared request/response contract, and the end-to-end verification flow.

INFO

Every sample in this documentation uses the same fictional identity — Nguyễn Văn A, NID 001xxxxxxxxx, user_id user_00000001, ref_id ref_00000001. Credentials, tokens and keys appear as <PLACEHOLDER>. None of it is real data; replace it all with your own values.

Prerequisites ​

Before calling any endpoint you need a tenant provisioned by CAKE and the credentials issued with it.

WhatDescription
Access IDA UUID that uniquely identifies your Tenant. Sent in the Authorization header. Provided by CAKE.
Access KeyThe secret key used to sign every request and to verify every response signature. Provided by CAKE.
Base URLIssued by CAKE together with your credentials, for both stage and production.
Tenant configurationThresholds, collections and enabled checks are configured per tenant by CAKE before go-live.
Client IDX-Client-ID value issued alongside the credentials, sent on gateway-fronted requests.

Contact CAKE Support to obtain access_id and access_key.

Samples throughout this documentation write the base URL as <BASE_URL> — substitute the value above.

WARNING

The Access Key is a server-side secret. Never embed it in a mobile app, a browser bundle, or any client the end user controls — sign requests from your backend only. Mobile clients authenticate with a short-lived JWT token instead (see below).

Core Concepts ​

Transaction Lifecycle ​

Every eKYC session runs inside a transaction, which moves through three states.

FieldTypeDescription
NOT_VERIFIEDstringThe transaction has been created and a token issued, but verification is not complete.
VERIFIEDstringThe authentication key was redeemed successfully.
EXPIREDstringThe transaction was not completed before its token expired.
WhatDescription
Create TransactionYour backend opens a transaction and receives the JWT the SDK will use.
Get TransactionReads the current status at any time.
Verify Transaction KeyRedeems the authentication key and closes the transaction.

A transaction is optional for a bare image check, but required whenever you need a verifiable, auditable result your backend can trust.

Authentication ​

eKYC supports two authentication schemes, chosen by who is calling.

Option 1: HMAC authentication (backend to eKYC backend) ​

Server-to-server requests are authenticated with HMAC-SHA256, which proves both the integrity and the authenticity of the request body using a secret shared by your backend and CAKE.

Two headers are required on every request:

HeaderDescription
X-TV-TimestampCurrent timestamp in RFC3339 format. Example: 2019-04-21T18:00:15+07:00
AuthorizationATV <ACCESS_ID>:<SIGNATURE>

The signature is a Base64-encoded HMAC-SHA256 of a canonical StringToSign:

text
StringToSign = HttpMethod + "\n" + UrlPath + "\n" + Timestamp + "\n" + HashedBody

# HttpMethod: POST, GET, PUT, PATCH or DELETE
# UrlPath:    relative route path, excluding the domain. Example: /v1/images
# Timestamp:  request time in RFC3339 format; must match the X-TV-Timestamp header
# HashedBody: hex_encode(SHA256(raw body))
#             Example (body `{}`): 44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a

Signature = Base64(HmacSha256(AccessKeySecret, Utf8EncodingOf(StringToSign)))

Reference implementation in JavaScript:

javascript
const CryptoJS = require("crypto-js");

const timestamp = new Date().toISOString(); // 2024-05-09T07:53:10.591Z
const access_key = "<ACCESS_KEY>";
const path = "/v1/create_ekyc_transaction";
const body = `{
    "user_id": "user_00000001",
    "ref_id": "ref_00000001"
}`;

function tokenize(method, path, timestamp, body, key) {
  const hashed_body = CryptoJS.SHA256(body).toString(CryptoJS.enc.Hex);
  const formatted = `${method}\n${path}\n${timestamp}\n${hashed_body}`;
  const hmac = CryptoJS.HmacSHA256(formatted, key);
  const hashed = hmac.toString(CryptoJS.enc.Hex);
  return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(hashed));
}

const authorize_token = tokenize("POST", path, timestamp, body, access_key);
// Send `timestamp` as X-TV-Timestamp and `authorize_token` as the signature — never log them.

Sample signed request:

bash
curl --location '<BASE_URL>/v1/create_ekyc_transaction' \
  --header 'Authorization: ATV <ACCESS_ID>:<SIGNATURE>' \
  --header 'X-TV-Timestamp: <TIMESTAMP>' \
  --header 'Content-Type: application/json' \
  --header 'X-Request-Id: <RANDOM_UUID>' \
  --header 'X-Client-ID: <CLIENT_ID>' \
  --data '{
    "user_id": "user_00000001",
    "ref_id": "ref_00000001"
}'

INFO

The StringToSign uses the hex-encoded SHA256 of the raw body. Some CAKE tenants are provisioned with a Base64-encoded body hash instead — confirm which variant your tenant uses with the CAKE team before implementing.

Option 2: JWT token (mobile SDK to eKYC backend) ​

A JWT token is issued during the eKYC step for one specific user and ref_id, and the Mobile SDK presents it when calling the eKYC Server directly. The token stops working once it expires.

Treat the token as opaque: pass it to the SDK as-is, and never parse, log or store it.

Response Signing ​

Every response from the eKYC Server is signed with HMAC-SHA256 over the method, path, timestamp and hashed response body, so your backend can prove the payload really came from CAKE.

HeaderDescription
X-Signature-TimestampSigned timestamp in RFC3339 format. Example: 2019-04-21T18:00:15+07:00
X-SignatureBase64 HMAC token from the CAKE server. Example: <SIGNATURE>
text
StringToSign = HttpMethod + "\n" + UrlPath + "\n" + Timestamp + "\n" + HashedBody

# HttpMethod: the method of the API that was called
# UrlPath:    relative route path, excluding the domain. Example: /v1/images
# Timestamp:  signed time in RFC3339 format, sent in X-Signature-Timestamp
# HashedBody: hex_encode(SHA256(raw response body))

Signature = Base64(HmacSha256(AccessKeySecret, Utf8EncodingOf(StringToSign)))

WARNING

Recompute and compare X-Signature before you trust a verdict. An unverified response is an unauthenticated one, and eKYC verdicts drive onboarding decisions.

How It Works ​

A full eKYC session spans four actors: the mobile app, your backend, the eKYC Backend, and the AI engines behind it. The diagram below shows the canonical happy path.

sequenceDiagram
    autonumber
    participant App as Mobile App / SDK
    participant BE as Your Backend
    participant EKYC as eKYC Backend
    participant AI as AI Engines

    Note over App,EKYC: Phase 1 — Capture & upload
    App->>BE: Captured ID card + portrait images
    BE->>EKYC: POST /v1/images (multipart, HMAC signed)
    EKYC-->>BE: image_id

    Note over BE,AI: Phase 2 — ID card verification
    BE->>EKYC: POST /v1/verify_id_card_sanity_sync
    EKYC->>AI: Sanity model
    AI-->>EKYC: verdict + score
    EKYC-->>BE: card_sanity verdict
    BE->>EKYC: POST /v1/read_id_card_info_sync
    EKYC-->>BE: card_information (OCR / MRZ / QR / NFC)
    BE->>EKYC: POST /v1/detect_id_card_tampering_sync
    EKYC-->>BE: card_tampering verdict + details

    Note over BE,AI: Phase 3 — Face verification
    BE->>EKYC: POST /v1/verify_portrait_sanity_sync
    EKYC-->>BE: portrait_sanity verdict
    BE->>EKYC: POST /v1/verify_face_liveness_sync
    EKYC-->>BE: is_live + score
    BE->>EKYC: POST /v1/compare_faces_sync (portrait vs ID photo)
    EKYC-->>BE: matched / unmatched / unsure + score

    Note over BE,EKYC: Phase 4 — Dedup & decision
    BE->>EKYC: POST /v1/search_faces_sync (duplicate check)
    EKYC-->>BE: similar faces + scores
    BE->>EKYC: POST /v1/index_faces_sync (enrol approved user)
    EKYC-->>BE: status success
    BE-->>App: Onboarding decision

Phase 1 — Capture and upload ​

  1. The mobile app or Mobile SDK captures the ID card front, the ID card back and a portrait selfie, then hands them to your backend.
  2. Your backend calls POST /v1/images once per image with the correct label. Each call returns an image_id.
  3. Your backend keeps the image_id values for the rest of the session — every downstream API accepts an image_id instead of re-uploading bytes.

Phase 2 — ID card verification ​

  1. POST /v1/verify_id_card_sanity_sync checks that the card image is usable: sharp enough, complete, not a photocopy, not glared over the data fields. Run this first — a bad image poisons every check after it.
  2. POST /v1/read_id_card_info_sync extracts the card fields via OCR, MRZ, QR and (where available) NFC, each with a confidence score.
  3. POST /v1/detect_id_card_tampering_sync looks for forgery: an altered portrait, unusual fonts, cross-source mismatches between MRZ, QR and OCR, and invalid ID-number or expiry logic.

Phase 3 — Face verification ​

  1. POST /v1/verify_portrait_sanity_sync checks that the selfie is usable: one face, well framed, eyes open, not occluded.
  2. POST /v1/verify_face_liveness_sync confirms a live person was in front of the camera rather than a photo or a screen replay.
  3. POST /v1/compare_faces_sync matches the selfie against the face printed on the ID card, returning matched, unmatched or unsure with a similarity score.

Phase 4 — Deduplication and decision ​

  1. POST /v1/search_faces_sync searches your face collection for anyone already enrolled with the same face — the standard duplicate-account and blocklist check.
  2. POST /v1/index_faces_sync adds the approved user's face to the collection so future sessions can find them.
  3. Your backend combines every verdict into a single onboarding decision and returns it to the app. eKYC returns evidence; the accept/reject policy stays yours.

Shared Response Envelope ​

Every JSON endpoint returns the same outer shape.

json
{
  "data": {
    "request_id": "f77bf948-fce3-48f3-b61c-396e4cd9a89b",
    "status": "success"
  },
  "errors": [],
  "success": true,
  "code": 0,
  "message": "Success"
}
FieldTypeDescription
dataobjectEndpoint-specific payload. Empty when the request failed before processing.
data.statusstringsuccess or failure — whether the request was processed successfully.
data.request_idstringServer-generated request ID. Quote it to CAKE Support when checking logs.
errorsarrayDetail of each failure. Empty on success.
errors[].codestringMachine-readable error code, e.g. image_not_found_exception.
errors[].messagestringHuman-readable description of the failure.
errors[].detailobjectOptional context, e.g. field naming the invalid parameter.
successboolWhether the call itself succeeded.
codeintNumeric status code — 0 on success.
messagestringStatus message.

An HTTP 200 does not mean the check passed. Read success, then data.status, then the verdict.

Error Codes ​

Numeric codes ​

CodeMessage
0Success
2001002Unexpected error occurred while processing the external request.
2001003Image sent to the server has no faces.
2001010Access ID not found.
2001014Register rate limit exceeded.
2001015Register brute force limit exceeded.
2001026Images sent to server are duplicated.
401100You are not authorized to perform the action.
4001000Input parameter violates a constraint.
4001001The X-TV-Timestamp header is expired, need a newer one.
4041000The image ID is not found in DB.
4041001User not found.
4091000The cus_user_id has been registered.
4131000The input image size exceeds the allowed limit (15MB).
4151000The provided image format is not supported (JPG/PNG).
4291000The number of requests exceeded your throughput limit.
5001000Some unexpected error occurs while processing the request.

String codes ​

CodeDescription
access_id_not_foundAccess ID not found.
access_denied_exceptionYou are not authorized to perform the action.
invalid_parameter_exceptionInput parameter violates a constraint.
request_time_too_skewedThe X-TV-Timestamp header is expired, need a newer one.
image_not_found_exceptionThe image ID is not found in DB.
image_too_large_exceptionThe input image size exceeds the allowed limit (15MB).
invalid_image_format_exceptionThe provided image format is not supported (JPG/PNG).
duplicated_imageImages sent to server are duplicated.
image_has_no_facesImage sent to the server has no faces.
request_not_found_exceptionThe request ID is not found when polling result by id.
request_timeout_exceptionRequest takes too long to process.
rate_limit_exceptionThe number of requests exceeded your throughput limit.
internal_server_errorSome unexpected error occurs while processing the request.

Next Steps ​

Start here

Open the session

Capture and upload

  • Upload Image — the mandatory first call that turns bytes into an image_id
  • Download Image — retrieve a previously uploaded image

ID card verification

Face verification

Deduplication

Close the session

Reference

Developers