Behavioral Biometrics (BehavioIQ)

Compute a behavior score

POST/v1/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

MethodPOST
Path/v1/behavior/score
Content-Typeapplication/json
Auth requiredYes — Access Key

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.

NameTypeRequiredDescription
challenge_idstringRequiredUUID of the challenge from create_challenge
transaction_dataobjectOptionalTransaction context for enhanced fraud scoring. See Transaction data below.
is_updatebooleanOptionalOverrides whether this call updates the user's behavioral profile (baseline). Omit for the default behavior. See below.

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_updateEffect
omitted / nullDefault behavior. The first session enrolls the baseline; later sessions update the profile only when your deployment's policy opts in.
trueAlways update. This session is folded into the baseline regardless of policy. On a first session this enrolls the baseline as usual.
falseNever 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_update takes priority over the default policy-driven behavior at every step. Use false for read-only scoring that must not mutate the profile, and true to 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):

JSON
{
  "challenge_id": "d4e5f6a7-b8c9-0123-4567-89abcdef0123",
  "is_update": false
}

Sample request

JSON
{
  "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") carrying more_details.is_first_session: true as 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_scorerisk_levelis_fraud
700 – 1000LOWfalse
400 – 699MEDIUMfalse
100 – 399HIGHfalse
0 – 99CRITICALtrue

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.

FieldTypeDescription
data.request_idstringUUID of the scoring request (for tracking)
data.behavior_scoreintegerBehavior score (0 – 1000). Higher means more similar to the enrolled baseline (more trustworthy)
data.risk_levelstringLOW, MEDIUM, HIGH, or CRITICAL (see mapping table above)
data.is_fraudbooleantrue only when risk_level is CRITICAL. Omitted when false — treat absence as false
data.is_confidentbooleanScoring confidence flag. Omitted when false
data.groupsobjectPer-signal-group breakdown, keyed by group name (device_description, device_behavior, user_behavior), each {score, weight}
data.explainersarrayScoring explainers (may be empty)
data.more_detailsobjectPresent on first-session responses only: {"is_first_session": true, "embedding_count": N}
data.server_infosobjectServer processing metadata
decisionobjectFinal action verdict. Present only when the behavior policy check is enabled (see Policy decision)
evaluation_detailsobjectApplied-policy details. Present only when the behavior policy check is enabled
errorsarrayList of errors (only on failure; data.status is failure in that case)

Verification session:

JSON
{
  "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:

JSON
{
  "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":

JSON
{
  "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:

JSON
{
  "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"
      }
    ]
  }
}
FieldTypeDescription
decision.final_actionstringALLOW, STEP_UP, REVIEW, or BLOCK — the recommended action for this session
decision.action_payloadobjectAction parameters, or null. For STEP_UP: {"challenge_type": "SOFT_OTP" \| "FACE_AUTHEN"}
evaluation_details.applied_policiesarrayPolicies that matched this session (empty array when no policy matched)
applied_policies[].policy_idstringStable policy identifier
applied_policies[].policy_namestringHuman-readable policy name
applied_policies[].policy_versionstringPolicy version string
applied_policies[].policy_outcomestringHow 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_levelDefault final_actionchallenge_type
LOWALLOW
MEDIUMSTEP_UPSOFT_OTP
HIGHSTEP_UPFACE_AUTHEN
CRITICALREVIEW

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 decision nor evaluation_details is present and the response keeps the legacy data-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.

Transaction data

transaction_data carries the payment context behind a scored session. It is optional and purely additive: omitting it leaves the request, and the returned score, exactly as they are today. What it adds is context for the behavior-score policy rules configured for your account — those rules can read every field below, so a policy can, for example, require a step-up challenge when transaction_amount exceeds a threshold, including on first-session (enrollment) calls. See Policy decision above.

Every field is optional — send the ones you have. Fields marked encrypted at rest are stored encrypted by the server: send them in plaintext, and policy rules still evaluate them in plaintext.

Transaction core

FieldTypeISO 20022 pathDescription
transaction_idstringPmtId/EndToEndIdYour identifier for the transaction
transaction_timestampstringGrpHdr/CreDtTmWhen the transaction was created, as an ISO 8601 timestamp
transaction_amountnumberIntrBkSttlmAmtSettlement amount
currency_codestringIntrBkSttlmAmt/@CcyISO 4217 currency code, e.g. VND
transaction_typestringPmtTpInfCoarse transaction type, e.g. transfer
channelstringChannel the transaction was initiated from, e.g. mobile_app
transaction_statusstringpacs.002 TxStsStatus of the transaction

Parties & accounts

FieldTypeISO 20022 pathDescription
account_numberstringDbtrAcct/IdSender account number — encrypted at rest
account_holder_namestringDbtr/NmSender account holder name — encrypted at rest
bank_codestringDbtrAgt/FinInstnId/(BICFI or ClrSysMmbId)Sender bank code — encrypted at rest
beneficiary_account_numberstringCdtrAcct/IdRecipient account number — encrypted at rest
beneficiary_account_holder_namestringCdtr/NmRecipient account holder name — encrypted at rest
beneficiary_bank_codestringCdtrAgt/FinInstnId/(BICFI or ClrSysMmbId)Recipient bank code — encrypted at rest

Risk & payment context

FieldTypeISO 20022 pathDescription
is_new_beneficiaryboolWhether this is the first payment to this beneficiary
days_since_beneficiary_addedintDays since the beneficiary was added to the sender's payee list
beneficiary_known_mule_flagboolWhether the beneficiary is on your known-mule list
beneficiary_name_match_resultstringOutcome of the beneficiary name check (e.g. NAPAS name-check)
beneficiary_proxy_aliasstringCdtrAcct/Prxy/IdBeneficiary proxy alias, e.g. phone or VietQR alias — encrypted at rest
beneficiary_countrystringCdtr/PstlAdr/CtryBeneficiary country
beneficiary_bank_countrystringCdtrAgt/FinInstnId/PstlAdr/CtryBeneficiary bank country
beneficiary_account_open_datestringWhen the beneficiary account was opened
relationship_to_beneficiarystringDeclared relationship between sender and beneficiary
directionstringCdtDbtIndCredit or debit indicator (CRDT / DBIT)
purpose_codestringPurp/CdPayment purpose code
transaction_categorystringPmtTpInf/CtgyPurp/CdCategory purpose code
transaction_remarkstringRmtInf/UstrdUnstructured remittance information / transfer note
payment_railstringPmtTpInf/LclInstrm/CdPayment rail or local instrument
initiation_methodstringHow the payment was initiated
merchant_category_codestringMrchntCtgyCdMerchant category code
original_amountnumberInstdAmtInstructed amount, before any conversion or fees
napas_system_trace_refstringPmtId/ClrSysRefClearing-system reference
global_tx_uuidstringPmtId/UETREnd-to-end unique transaction reference (UETR)
is_recurringboolWhether the payment is part of a recurring series
sender_countrystringDbtr/PstlAdr/CtrySender country
sender_account_open_datestringWhen the sender account was opened
sender_date_of_birthstringDbtr/Id/PrvtId/DtAndPlcOfBirth/BirthDtSender date of birth — encrypted at rest
customer_risk_levelstringYour own risk rating for the customer
balance_beforenumberAccount balance before the transaction
balance_afternumberAccount balance after the transaction
time_since_last_inflowintSeconds since the last credit into the account
velocity_featuresjson_objectFree-form velocity features you compute for the customer
txn_velocity_windowjson_objectFree-form transaction counts/amounts over your own time windows
failed_auth_attemptsintFailed authentication attempts leading up to this transaction
days_since_credential_changeintDays since the customer last changed a credential
sim_swap_recent_flagboolWhether a recent SIM swap was detected for the customer
authentication_methodstringAuthentication method used for the payment itself
biometric_required_flagboolWhether your own policy already requires biometrics for this transaction
ip_addressstringIP address the transaction was initiated from

Field naming: canonical names and ISO 20022 paths

Each field can be keyed by either its canonical name (the Field column above) or the ISO 20022 path shown next to it; both are normalized to the canonical name on ingest. If the same logical field arrives under both keys in one request, the ISO-keyed value wins.

  • Canonical names are matched case-insensitively, so channel and Channel both bind. ISO paths must match exactly as written above.
  • Acct/OpngDt and Bal/Amt are deliberately not accepted as aliases — each maps to two canonical fields (sender vs. beneficiary open date, balance before vs. after) and cannot be told apart from the key alone. Use the canonical names for those four fields.
  • Keys that are not listed above are kept verbatim and stay readable by policy rules, so bank-specific fields can be sent alongside the standard ones.