Behavioral Biometrics (BehavioIQ)
Compute a behavior score
Finalizes a challenge and returns the behavior score and risk level for the session, plus the recommended action when the policy check is enabled.
Request
A server-side call that finalizes the challenge and triggers the scoring engine to analyze the collected behavioral data. Unlike the two SDK endpoints it takes plain JSON with Access Key authentication — no E2E encryption.
| Name | Type | Required | Description |
|---|---|---|---|
challenge_id | string | Required | UUID of the challenge from create_challenge |
transaction_data | object | Optional | Transaction context for enhanced fraud scoring |
is_update | boolean | Optional | Overrides whether this call updates the user's behavioral profile (baseline). Omit for the default behavior. See below. |
transaction_data fields:
| Name | Type | Required | Description |
|---|---|---|---|
transaction_id | string | Optional | Unique transaction identifier |
transaction_timestamp | string | Optional | ISO 8601 timestamp |
transaction_amount | number | Optional | Transaction amount |
currency_code | string | Optional | ISO 4217 currency code (e.g. VND) |
transaction_type | string | Optional | Type of transaction |
channel | string | Optional | Transaction channel |
transaction_status | string | Optional | Status of the transaction |
account_number | string | Optional | Sender account number (encrypted at rest) |
account_holder_name | string | Optional | Sender account holder name (encrypted at rest) |
bank_code | string | Optional | Sender bank code (encrypted at rest) |
beneficiary_account_number | string | Optional | Recipient account number (encrypted at rest) |
beneficiary_account_holder_name | string | Optional | Recipient account holder name (encrypted at rest) |
beneficiary_bank_code | string | Optional | Recipient bank code (encrypted at rest) |
Fields marked "encrypted at rest" are automatically encrypted by the server before storage. You should send them in plaintext.
transaction_datais also visible to behavior policies (see Policy decision below). For example, a policy can require a step-up challenge whentransaction_amountexceeds a threshold — including on first-session (enrollment) calls.
The is_update parameter
By default, the user's behavioral profile (the enrolled baseline) is maintained automatically: the first session enrolls the baseline, and later sessions refresh it according to the policy configured for your deployment. The optional is_update flag lets your backend override this on a per-call basis.
is_update | Effect |
|---|---|
omitted / null | Default behavior. The first session enrolls the baseline; later sessions update the profile only when your deployment's policy opts in. |
true | Always update. This session is folded into the baseline regardless of policy. On a first session this enrolls the baseline as usual. |
false | Never update. No change is made to the profile. On a first session (no baseline yet) no baseline is built — the response is still the synthetic first-session shape but with more_details.embedding_count: 0. On a later session the session is scored normally but read-only (the baseline is left untouched). |
Once set,
is_updatetakes priority over the default policy-driven behavior at every step. Usefalsefor read-only scoring that must not mutate the profile, andtrueto force-enroll a known-good session. Omit the field to keep the default behavior — existing integrations need no change.
Read-only scoring (no profile update):
{
"challenge_id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123",
"is_update": false
}
Sample request
{
"challenge_id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123",
"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"
}
}
Response
data.behavior_score carries the verdict and data.risk_level bands it. Scoring follows an enrollment / verification model, like fingerprint enrollment:
- First session (enrollment). The first scored challenge for a
(cus_user_id, client)pair builds the user's behavioral baseline. No real scoring takes place — the response is a synthetic safe default (behavior_score: 1000,risk_level: "LOW") carryingmore_details.is_first_session: trueas the unambiguous marker. Defer first-session risk decisions to your other layers (KYC, device, biometric). - Subsequent sessions (verification). Each later session is compared against the enrolled baseline. The score answers: "how similar is this session's behavior to the enrolled behavior?"
behavior_score | risk_level | is_fraud |
|---|---|---|
| 700 – 1000 | LOW | false |
| 400 – 699 | MEDIUM | false |
| 100 – 399 | HIGH | false |
| 0 – 99 | CRITICAL | true |
Calling this endpoint again for an already-scored challenge re-scores it against the current baseline (the call is not idempotent); each call creates its own
request_id.
| Field | Type | Description |
|---|---|---|
data.request_id | string | UUID of the scoring request (for tracking) |
data.behavior_score | integer | Behavior score (0 – 1000). Higher means more similar to the enrolled baseline (more trustworthy) |
data.risk_level | string | LOW, MEDIUM, HIGH, or CRITICAL (see mapping table above) |
data.is_fraud | boolean | true only when risk_level is CRITICAL. Omitted when false — treat absence as false |
data.is_confident | boolean | Scoring confidence flag. Omitted when false |
data.groups | object | Per-signal-group breakdown, keyed by group name (device_description, device_behavior, user_behavior), each {score, weight} |
data.explainers | array | Scoring explainers (may be empty) |
data.more_details | object | Present on first-session responses only: {"is_first_session": true, "embedding_count": N} |
data.server_infos | object | Server processing metadata |
decision | object | Final action verdict. Present only when the behavior policy check is enabled (see Policy decision) |
evaluation_details | object | Applied-policy details. Present only when the behavior policy check is enabled |
errors | array | List of errors (only on failure; data.status is failure in that case) |
Verification session:
{
"data": {
"request_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"behavior_score": 850,
"risk_level": "LOW",
"groups": {
"device_description": { "score": 800, "weight": 0.2 },
"device_behavior": { "score": 860, "weight": 0.4 },
"user_behavior": { "score": 855, "weight": 0.4 }
},
"explainers": [],
"server_infos": {}
}
}
First session — enrollment:
{
"data": {
"request_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"behavior_score": 1000,
"is_fraud": false,
"risk_level": "LOW",
"groups": {
"device_description": { "score": 0, "weight": 0.2 },
"device_behavior": { "score": 0, "weight": 0.4 },
"user_behavior": { "score": 0, "weight": 0.4 }
},
"is_confident": false,
"explainers": [],
"more_details": {
"is_first_session": true,
"embedding_count": 1
}
}
}
Scoring failure — HTTP 200, data.status: "failure":
{
"data": {
"status": "failure",
"request_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"server_infos": {}
},
"errors": [
{
"message": "<error description from the scoring engine>",
"code": "<engine-defined error code>"
}
]
}
Policy decision (decision + evaluation_details)
Deployments can enable a behavior-score policy check per client or access key (configured by TrustVision operators — contact your integration manager to enable it). When enabled, every response — including first-session responses — carries two additional top-level keys next to data:
{
"data": { "...": "unchanged DS payload as above" },
"decision": {
"final_action": "STEP_UP",
"action_payload": { "challenge_type": "FACE_AUTHEN" }
},
"evaluation_details": {
"applied_policies": [
{
"policy_id": "bp_critical_face_authen",
"policy_name": "CRITICAL risk → Face Authentication",
"policy_version": "2026.06.04.1",
"policy_outcome": "OVERRIDDEN"
}
]
}
}
| Field | Type | Description |
|---|---|---|
decision.final_action | string | ALLOW, STEP_UP, REVIEW, or BLOCK — the recommended action for this session |
decision.action_payload | object | Action parameters, or null. For STEP_UP: {"challenge_type": "SOFT_OTP" \| "FACE_AUTHEN"} |
evaluation_details.applied_policies | array | Policies that matched this session (empty array when no policy matched) |
applied_policies[].policy_id | string | Stable policy identifier |
applied_policies[].policy_name | string | Human-readable policy name |
applied_policies[].policy_version | string | Policy version string |
applied_policies[].policy_outcome | string | How the policy adjusted the default action: NO_CHANGE, ESCALATED, DOWNGRADED, or OVERRIDDEN |
When no policy matches, final_action falls back to the default mapping from risk_level:
risk_level | Default final_action | challenge_type |
|---|---|---|
LOW | ALLOW | — |
MEDIUM | STEP_UP | SOFT_OTP |
HIGH | STEP_UP | FACE_AUTHEN |
CRITICAL | REVIEW | — |
Policies are authored per deployment and can reference the full scoring context, including transaction_data (e.g. "require face authentication when a first-session transaction exceeds 1,000,000 VND").
Backward compatibility: when the policy check is not enabled (the default), neither
decisionnorevaluation_detailsis present and the response keeps the legacydata-only shape. Integrations should ignore unknown top-level keys.
Failure codes
Scoring failures (engine-side) return HTTP 200 with data.status: "failure" and engine-defined error codes. Request-validation errors (e.g. no_batch_events, challenge_not_found) return HTTP 4xx/503 with an errors-only body — {"errors": [{"code": "...", "message": "..."}]}, no data block.
The envelope is described under Error Responses, and the codes every Behavioral call can return are listed under Common Error Codes.