Skip to main content

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"
}
At least one of 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.

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 stability — the 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

Wait approximately 2 seconds after starting the workflow before the first poll. The system needs a moment to initialize nodes.
{
  "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:

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
Upload images first via the File Upload API to obtain S3 URIs.

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\"}"
}
Important: 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"
}
For phone OTP, set "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.

For optional documents, use 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"
}
Cancellation is irreversible. A cancelled execution cannot be resumed.

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.

Cancellation contract pending confirmation: the lifecycle reference uses CANCELED while the KYC reference uses CANCELLED. Stop on a confirmed cancellation, but confirm the exact API enum with WIDTH engineering before deploying cancellation-dependent code. Neither spelling is established here as definitive.