POST /api/account/integration/kyc/transaction
sdk_token your app needs to run the
flow. This is where you declare what should be verified and what counts as acceptable: the whole configuration is documented on this page.
Request
string
required
Bearer {access_token} — see Authentication.string (UUID)
required
The customer this verification belongs to, from
Create customer.
string
required
Which checks to run.
DOCUMENT_AND_BIOMETRIC— document capture and livenessDOCUMENT_ONLY— document capture onlyBIOMETRIC_ONLY— liveness only
object
The rules to enforce. Every field is optional and falls back to the default listed below, so
{} is valid. Build it on your server from your own records — never from values sent by the app.Show config
Show config
integer
default:"1440"
How long the user has to complete the flow, in minutes, counted from creation. Range
1–1440. When it elapses the transaction becomes EXPIRED and the sdk_token stops working.object
object
How each device-integrity signal affects the decision. Every field takes
REJECT, WARN or IGNORE. Applies to all three transaction types.Show device_policy
Show device_policy
string
default:"REJECT"
The device is rooted, jailbroken, or running under a debugger. Emits
COMPROMISED_DEVICE.string
default:"WARN"
Developer mode is enabled. Emits
DEVELOPER_MODE_ENABLED.string
default:"REJECT"
The capture was made on an emulator or simulator. Emits
EMULATOR_DETECTED.string
default:"WARN"
The connection is routed through a VPN, a proxy or Tor. Emits
VPN_OR_PROXY_DETECTED.string
default:"REJECT"
The reported GPS location is simulated. Emits
MOCK_LOCATION_DETECTED. Requires the SDK to request location permission.string
default:"WARN"
The GPS country differs from the IP country. Emits
GEO_IP_MISMATCH. Requires the SDK to request location permission.object
Which documents are accepted, and what the extracted data must match. Applies to
DOCUMENT_AND_BIOMETRIC and DOCUMENT_ONLY; ignored for BIOMETRIC_ONLY.Show document_policy
Show document_policy
object
Omit it to accept every supported document.
Show document_eligibility
Show document_eligibility
string
ALLOW — only the listed documents are accepted. REJECT — every supported document is accepted except the listed ones.array
The list
filter_mode operates on. Each entry:country(string, required) — ISO 3166-1 alpha-2, for exampleAR.type(string, required) —NATIONAL_ID,PASSPORT,DRIVER_LICENSE,RESIDENT_PERMITorTEMP_PERMIT_ID.allow_expired(boolean, required) — whether an expired document of this exact country and type is accepted. Whenfalse, an expired document emitsEXPIRED_DOCUMENT.
DOCUMENT_TYPE_NOT_SUPPORTED; one that is supported but excluded here emits DOCUMENT_NOT_ELIGIBLE.object
Values the document must agree with. Every field is optional; every mismatch is a
REJECT.Show data_matching
Show data_matching
object
{ "value": string, "similarity_min_score": number }. Compared by text similarity; similarity_min_score ranges 0.1–1.0, where 1.0 is an exact match. Emits NAME_MISMATCH with { score, threshold }.object
Same shape and semantics as
given_names. Emits SURNAME_MISMATCH.string
M, F or X. Exact match. Emits GENDER_MISMATCH.string
NATIONAL_ID, PASSPORT, DRIVER_LICENSE, RESIDENT_PERMIT or TEMP_PERMIT_ID. Exact match. Emits EXPECTED_DOCUMENT_TYPE_MISMATCH.string
ISO 3166-1 alpha-2. Exact match. Emits
DOCUMENT_ISSUING_COUNTRY_MISMATCH.string
Alphanumeric only — no dots, hyphens or spaces; chmod normalises the extracted number the same way. Emits
DOCUMENT_NUMBER_MISMATCH.string
ISO-8601 date,
YYYY-MM-DD. Exact match. Emits DATE_OF_BIRTH_MISMATCH.object
Liveness and face-comparison thresholds, expected age and gender. Applies to
DOCUMENT_AND_BIOMETRIC and BIOMETRIC_ONLY; ignored for DOCUMENT_ONLY.Show biometric_policy
Show biometric_policy
number
default:"0.85"
Minimum confidence that a real, present person was captured. Range
0.1–1.0. Below it, LIVENESS_LOW_CONFIDENCE is emitted.number
default:"0.85"
Minimum confidence for two faces to count as the same person. Range
0.1–1.0. Applies to every comparison. A failed comparison emits DOCUMENT_VS_LIVENESS_FACE_MISMATCH, LIVENESS_VS_IDENTITY_REF_FACE_MISMATCH or DOCUMENT_VS_IDENTITY_REF_FACE_MISMATCH.boolean
default:"true"
Whether face comparison runs at all.
false skips every comparison and emits the informational FACE_COMPARISON_DISABLED.string | null
default:"null"
MALE, FEMALE, or null to accept any. Estimated from the selfie. Emits LIVENESS_GENDER_MISMATCH_EXPECTED.integer | null
default:"null"
Minimum accepted age,
0–200. Estimated from the selfie. Emits LIVENESS_AGE_OUT_OF_RANGE.integer | null
default:"null"
Maximum accepted age,
0–200. Must not be lower than expected_min_age.string
default:"REJECT"
REJECT, WARN or IGNORE when the face already belongs to a different customer in your account. Accepted and validated, not enforced yet.object
How to react to a face on a blocklist. Both fields take
REJECT, WARN or IGNORE. Accepted and validated, not enforced yet.Response
string (UUID)
The transaction identifier. Use it to
fetch the result; it also arrives on the
webhook and is returned to your app by the SDK.
string (JWT)
Pass this to your mobile app and hand it to the SDK. Scoped to this transaction only.
string
Always
Bearer.integer
SDK token lifetime in seconds, derived from
transaction_ttl_minutes.Example
A full onboarding configuration, with every policy set:curl -X POST https://{your-api-host}/api/account/integration/kyc/transaction \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "4a63b11c-803d-4126-bf91-d5f8290ff0a5",
"transaction_type": "DOCUMENT_AND_BIOMETRIC",
"config": {
"transaction_ttl_minutes": 60,
"webhook": {
"url": "https://api.example.com/hooks/chmod"
},
"device_policy": {
"on_compromised_device": "REJECT",
"on_developer_mode": "WARN",
"on_emulator": "REJECT",
"on_vpn_or_proxy": "WARN",
"on_gps_mock_location": "REJECT",
"on_gps_ip_location_mismatch": "WARN"
},
"document_policy": {
"document_eligibility": {
"filter_mode": "ALLOW",
"target_documents": [
{ "country": "AR", "type": "NATIONAL_ID", "allow_expired": false },
{ "country": "AR", "type": "PASSPORT", "allow_expired": false }
]
},
"data_matching": {
"given_names": { "value": "Ana Maria", "similarity_min_score": 0.85 },
"surnames": { "value": "Perez", "similarity_min_score": 0.85 },
"gender": "F",
"document_type": "NATIONAL_ID",
"document_issuing_country": "AR",
"document_number": "20123456",
"date_of_birth": "1996-01-09"
}
},
"biometric_policy": {
"liveness_min_score": 0.9,
"face_comparison_min_score": 0.9,
"require_face_comparison": true,
"expected_gender": null,
"expected_min_age": 18,
"expected_max_age": 120,
"on_another_customer_repeated_face": "REJECT"
},
"blacklist_policy": {
"on_internal_face_blacklist_match": "REJECT",
"on_global_face_blacklist_match": "REJECT"
}
}
}'
const res = await fetch(`${CHMOD_API_URL}/api/account/integration/kyc/transaction`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
customer_id: user.chmodCustomerId,
transaction_type: "DOCUMENT_AND_BIOMETRIC",
config: {
transaction_ttl_minutes: 60,
webhook: { url: `${process.env.PUBLIC_URL}/hooks/chmod` },
device_policy: {
on_compromised_device: "REJECT",
on_emulator: "REJECT",
on_vpn_or_proxy: "WARN",
},
document_policy: {
document_eligibility: {
filter_mode: "ALLOW",
target_documents: [
{ country: "AR", type: "NATIONAL_ID", allow_expired: false },
],
},
// Match what the document says against what you already hold on file.
data_matching: {
given_names: { value: user.firstName, similarity_min_score: 0.85 },
surnames: { value: user.lastName, similarity_min_score: 0.85 },
date_of_birth: user.dateOfBirth,
},
},
biometric_policy: {
liveness_min_score: 0.9,
face_comparison_min_score: 0.9,
require_face_comparison: true,
},
},
}),
});
const { transaction_id, sdk_token } = await res.json();
res = requests.post(
f"{CHMOD_API_URL}/api/account/integration/kyc/transaction",
headers={"Authorization": f"Bearer {access_token}"},
json={
"customer_id": user.chmod_customer_id,
"transaction_type": "DOCUMENT_AND_BIOMETRIC",
"config": {
"transaction_ttl_minutes": 60,
"webhook": {"url": f"{PUBLIC_URL}/hooks/chmod"},
"document_policy": {
"document_eligibility": {
"filter_mode": "ALLOW",
"target_documents": [
{"country": "AR", "type": "NATIONAL_ID", "allow_expired": False},
],
},
},
"biometric_policy": {
"liveness_min_score": 0.9,
"require_face_comparison": True,
},
},
},
)
data = res.json()
Response
{
"transaction_id": "34dc4204-2b57-42ae-a3bc-1d114935b98f",
"sdk_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Defaults
What you get when you omit a field entirely:| Field | Default |
|---|---|
transaction_ttl_minutes | 1440 (24 hours) |
webhook | none — no notification is sent |
device_policy.on_compromised_device | REJECT |
device_policy.on_developer_mode | WARN |
device_policy.on_emulator | REJECT |
device_policy.on_vpn_or_proxy | WARN |
device_policy.on_gps_mock_location | REJECT |
device_policy.on_gps_ip_location_mismatch | WARN |
document_policy.document_eligibility | every supported document is accepted |
document_policy.data_matching | nothing is matched |
biometric_policy.liveness_min_score | 0.85 |
biometric_policy.face_comparison_min_score | 0.85 |
biometric_policy.require_face_comparison | true |
biometric_policy.expected_gender | null (any) |
biometric_policy.expected_min_age / expected_max_age | null (any) |
biometric_policy.on_another_customer_repeated_face | REJECT |
blacklist_policy.on_internal_face_blacklist_match | REJECT |
blacklist_policy.on_global_face_blacklist_match | REJECT |
Going deeper
Each policy has a reference page with the supporting detail — the supported document matrix, how the three face comparisons work, what happens when a device signal is missing, and how to pick thresholds:Device policy
Document policy
Biometric policy
Blacklist policy
Next
Get transaction
Read the status and the decision.
Webhooks
Be notified as soon as the decision exists.

