KYC · Customer onboarding
Register a customer, recover or start an execution, submit node data and track the case decision.
Flow overview
Use eventType KYC_EVENT. Register the customer, check for an existing execution, start only when no match exists, submit each waiting node and track the final case decision. KYC is a Console display name.
Step 1 — Register Customer
Endpoint:
workflow.applicant.register
POST
{
"customerId": "C_abc123",
"email": "john@example.com"
}
customerId or email is
required. This API is idempotent — repeated
calls return the existing customer.
Response:
{
"apiCode": 200,
"data": {
"code": 200,
"data": { "customerId": "C_abc123", "isNew": true },
"msg": "success"
}
}
2. Resume or start a workflow
2.1 Check for an existing execution
Endpoint:
workflow.apply.query
POST
Looks up whether a customer already has an execution by
eventType + source + customerId. Read-only — it
starts no new execution and is not billed. Use
apiVersion 1.0.0.
-
Resume after a break — if
submittimed out or the process crashed before you got anexecutionId: query first; on a hit, recover theexecutionIdand resume polling, avoiding a duplicate (billed) submit. -
Deduplicate submits — when an upstream retry
may re-fire for the same customer: skip
submitif the query returnsRUNNING/APPROVED.
Request
{
"eventType": "KYC_EVENT",
"source": "API",
"customerId": "C_abc123"
}
| Field | Type | Required | Description |
|---|---|---|---|
eventType |
string | Yes | Same as at submit time. |
source |
string | Yes | Same as at submit time. |
customerId |
string | Yes |
Same as data.customerId at submit time (must stay
stable for the same customer).
|
Response
{
"apiCode": 200,
"apiMsg": "OK",
"data": {
"code": 200,
"executionId": "exec-7208f03b931e4897",
"status": "RUNNING"
}
}
| Match | executionId |
status |
Suggested action |
|---|---|---|---|
| Customer already approved | most recent COMPLETED execution | APPROVED |
Do not submit again. |
| An execution is in flight | that execution | RUNNING |
Switch to polling workflow.execution.status.
|
| No match (rejected or no record) | empty | — |
Call submit normally.
|
customerId must exactly match the
data.customerId used at submit time and stay stable for
the same customer over time; otherwise the recovery query will never
find a match.
2.2 Start the workflow
Endpoint:
workflow.apply.submit
POST
{
"eventType": "KYC_EVENT",
"source": "API",
"data": {
"customerId": "C_abc123",
"referenceId": "onboarding-uuid-001"
}
}
Store the returned executionId — it is required for
all subsequent API calls. For apiVersion 2.0.0 and the Case
decision fields (caseStatus / riskScore) this
workflow shares, see
Workflow Lifecycle API → Submit
and Status & Case Decision.
Use apiVersion 2.0.0 for workflow.apply.submit. workflowId is optional: provide a published workflow ID to select it explicitly, or leave it empty for eventType + source routing. Persist executionId. A business data.code of 403 means the event quota is exhausted; ask an administrator to replenish it before submitting again.
Step 3 — Poll Status
Endpoint:
workflow.execution.status
POST
{
"executionId": "exec-abc123def456"
}
| Status | Meaning | Action |
|---|---|---|
RUNNING |
Executing, no node waiting | Continue polling (2–5 sec) |
WAITING |
Node waiting for input |
Read currentNode and submit data
|
COMPLETED |
Workflow finished | Read the final result and caseStatus; apply the Case decision exit rule below. |
FAILED |
Execution failed | Stop polling, contact support |
TIMEOUT |
Exceeded time limit | Stop polling, contact support |
| cancellation value: pending confirmation | Execution cancelled | Stop polling |
Key fields when status = WAITING:
-
currentNode.nodeType→ determines which API to call -
currentNode.callbackKey→ required when submitting data -
currentNode.metadata→ node-specific configuration
Read caseNo, caseStatus, riskScore and resolvedAt as well as execution status. UNASSIGNED, UNDER_REVIEW and RETURNED indicate manual handling; OTHER_GROUP means processing continues in another group or node. Continue polling until caseStatus is APPROVED, REJECTED or CLOSED, or status is FAILED or a confirmed cancellation. During manual handling use 30–60 seconds; during automated processing use 3–5 seconds. An execution status alone is not a substitute for the Case decision.
Step 4 — Submit Node Data
Each submission response includes a nextStep object. Use it
directly to proceed —
no need to poll between every submission. Only poll
when recovering a session or waiting for the final result.
| nodeType | API | Description |
|---|---|---|
flow_action_identity_document |
workflow.kyc.ocr →
workflow.kyc.ocr.data
|
Upload document images, retrieve OCR results |
flow_action_individual_particular |
workflow.kyc.particular.data |
Submit personal information |
flow_action_liveness_test |
workflow.kyc.liveness.data |
Submit PASS with a customer photo |
flow_action_validate_email |
workflow.kyc.otp.send +
workflow.kyc.otp.verify
|
Email verification via OTP |
flow_action_validate_phone |
workflow.kyc.otp.send +
workflow.kyc.otp.verify
|
Phone verification via OTP |
flow_action_individual_required_document |
workflow.kyc.required.doc + workflow.kyc.required.doc.submit | Upload supporting documents |
flow_action_individual_nonrequired_document |
workflow.kyc.non.required.doc + workflow.kyc.non.required.doc.submit | Optional documents (can skip) |
flow_action_individual_required_forms |
workflow.kyc.required.form + workflow.kyc.required.form.submit | Submit form/questionnaire answers |
flow_action_screen_check |
(automatic) | AML/sanctions screening — no action needed |
Identity Document (OCR)
Two-step: submit images, then poll results.
Step 1 — Submit:
workflow.kyc.ocr
POST
| Parameter | Type | Required | Description |
|---|---|---|---|
provider |
String | Yes |
WIDTH / REGULA / VERIFF
|
frontImage |
String | Yes | S3 URI of document front image |
backImage |
String | No | S3 URI of document back image |
documentType |
String | Yes |
NATIONAL_ID / INTERNATIONAL_PASSPORT /
DRIVING_LICENCE / RESIDENCE_PERMIT
|
region |
String | Yes | ISO 2-letter country code |
executionId |
String | Yes | Execution ID |
callbackKey |
String | Yes |
From currentNode.callbackKey
|
referenceId |
String | Yes | Your tracking ID |
includeDocverCheck |
Boolean | No | Enable document verification |
OCR submission body: workflow.kyc.ocr
{
"provider": "WIDTH",
"frontImage": "s3://workflow-kyc/.../front.jpg",
"backImage": "s3://workflow-kyc/.../back.jpg",
"documentType": "INTERNATIONAL_PASSPORT",
"region": "SG",
"executionId": "exec-abc123def456",
"callbackKey": "kyc:ocr:exec-abc123def456:...",
"referenceId": "onboarding-uuid-001"
}
Step 2 — Poll Results:
workflow.kyc.ocr.data
POST
Poll after 2–5 seconds. OCR typically completes within 5–15 seconds.
Particular
Endpoint:
workflow.kyc.particular.data
POST
{
"executionId": "exec-abc123def456",
"callbackKey": "kyc:particular:exec-abc123def456:...",
"referenceId": "onboarding-uuid-001",
"personData": "{\"name\":\"John Smith\",\"dateOfBirth\":\"1990-01-15\",\"nationality\":\"SG\",\"sex\":\"MALE\",\"countryOfResidence\":\"SG\",\"address\":\"123 Main St\"}"
}
personData is a
JSON-encoded string, not a nested object. You must
JSON.stringify() the data before placing it in the
request body.
Liveness
For OpenAPI, liveness is a data submission step — not an
interactive check. Submit status: "PASS" with a customer
photo.
Endpoint:
workflow.kyc.liveness.data
POST
{
"provider": "SELF",
"status": "PASS",
"scores": "{\"qualityScore\":1.0,\"brightnessScore\":100,\"brightnessStatus\":\"normal\"}",
"images": "{\"faceFull\":\"s3://workflow-kyc/tenantImages/2026-04-16/customer_photo.jpg\"}",
"executionId": "exec-abc123def456",
"callbackKey": "kyc:liveness:exec-abc123def456:...",
"referenceId": "onboarding-uuid-001"
}
scores and images are
JSON strings. Upload the photo via
File Upload API first.
Email / Phone OTP
Step 1 — Send:
workflow.kyc.otp.send
POST
{
"type": "email",
"identity": "john@example.com",
"executionId": "exec-abc123def456",
"callbackKey": "kyc:email:exec-abc123def456:...",
"referenceId": "onboarding-uuid-001"
}
"type": "phone" and use E.164 format
for identity (e.g., +6581234567). Store the
returned requestId for the verify step.
Step 2 — Verify:
workflow.kyc.otp.verify
POST
{
"type": "email",
"identity": "john@example.com",
"code": "985024",
"executionId": "exec-abc123def456",
"callbackKey": "kyc:email:exec-abc123def456:...",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"referenceId": "onboarding-uuid-001"
}
Required Documents
Endpoint:
workflow.kyc.required.doc
POST
{
"doc": "{\"front\":{\"url\":\"s3://workflow-kyc/.../bill.pdf\",\"filename\":\"bill.pdf\",\"content_type\":\"application/pdf\",\"size\":85618}}",
"isRequired": true,
"docType": "UTILITY_TELEPHONE_BILL",
"extraData": "{\"issueDate\":\"2026-03-01\"}",
"executionId": "exec-abc123def456",
"callbackKey": "kyc:required_doc:exec-abc123def456:...",
"referenceId": "onboarding-uuid-001"
}
After uploading all documents, call
workflow.kyc.required.doc.submit to advance.
workflow.kyc.non.required.doc with the same schema. Call
workflow.kyc.non.required.doc.submit to advance (even if skipping).
Required Forms
Endpoint:
workflow.kyc.required.form
POST
{
"formId": "IndividualForm",
"formData": "{\"field_source_of_funds\":\"EMPLOYMENT\",\"field_annual_income\":\"50000-100000\"}",
"executionId": "exec-abc123def456",
"callbackKey": "kyc:required_form:exec-abc123def456:...",
"referenceId": "onboarding-uuid-001"
}
Submit all forms, then call
workflow.kyc.required.form.submit.
Cancel execution
Endpoint:
workflow.execution.cancel
POST
{
"executionId": "exec-abc123def456"
}
Mid-Session Recovery
If your process is interrupted, call
workflow.execution.status with the stored
executionId to resume from the last pending node. No data
is lost.