Appearance
Individual KYC with documents
An individual opens an account: a two-step collection flow asks who they are and for a photo of an identity document, the document goes to a verifier app whose verdict arrives through the vendor's signed webhook, and a rule set turns the verdict into approve, review or reject. Pack: examples/policies/individual-kyc-with-documents/.
A pattern for expressing an onboarding policy with a document, not compliance guidance: which documents to accept, from whom and with what checks is the tenant's decision. The pack verifies with the deterministic doc-verify-mock app, which the development seed installs, so it runs on the development stack; a tenant without that install can import the pack, but its runs fail on unknown_app until the app is installed and enabled.
The decision it automates
Whether a person is onboarded on the strength of the details they typed and the document they uploaded, with a reviewer involved only when the verifier could not decide or the name does not match.
Data expected
- A subject of kind
user; the pack reads nothing fromsubject.data, everything comes from the flow. - The
kyc-individualcollection flow, two steps:fullName,dateOfBirth,country(select),email; thenidentityDocument(a requiredfilefield: JPEG, PNG, WebP or PDF up to 20 MB) and aconsentcheckbox. The applicant uploads the file through the presigned upload the API issues for the submission; the submit is refused until the document is scanned clean (see Documents).
Steps and rules
| Step | Type | Why |
|---|---|---|
collect | wait_for_collection | Pauses for kyc-individual; data under submission.*. |
verify | call_app | doc-verify-mock verify with documentId mapped from submission.identityDocument.fileId and applicant.fullName from submission.fullName (dotted keys set nested input). The action answers pending; the run waits for the vendor's signed webhook (waiting_callback) and resumes with the verdict under idv. |
rules | evaluate_rules | The kyc_individual rule set. |
route | branch | rules.outcome == manual_review opens a case. |
review | create_case | kyc_review, priority medium, 48-hour SLA: an inconclusive document is not urgent, but it must not wait a week. |
decide | emit_decision | From the rules, or the reviewer's decision. |
The rule set kyc_individual sums weights into bands 0-40 approve, 40-90 manual_review, 90-100 reject. Every expression rule is guarded with hasPath, so a rule never fails because the verifier has not answered; the workflow orders the steps so it always has.
| Rule | Type | Severity, weight | Fails when |
|---|---|---|---|
kyc_country_supported | comparison | block, 100 | submission.country is not one of US, GB, DE, FR (the flow offers BR to show a rejection) |
kyc_document_declined | expression | block, 100 | idv.outcome == "declined" (an expired document, a machine-readable zone that does not check out) |
kyc_document_inconclusive | expression | warn, 50 | idv.outcome == "review" (nothing readable on the document) |
kyc_name_mismatch | expression | warn, 40 | idv.checks.nameMatch == false; a null check (nothing read) is left to the inconclusive rule |
A declined document or an unsupported country rejects outright; an inconclusive document (50) goes to a reviewer, as does a document that reads fine but carries another name (40); both together (90) reject.
What a reviewer sees
A kyc_review case with the applicant's answers, the verifier's output (outcome, the three checks, the extracted name, document number and expiry) and the rule results. The document itself is available for download from the case, through a presigned URL that is audited.
How to adapt it
Verify with the sumsub app of the App catalogue instead of the mock, once its three secrets are stored: its verify takes the same documentId and the applicant as firstName, lastName and dateOfBirth, so ask for the two names separately in the flow and map applicant.dateOfBirth from submission.dateOfBirth. When Sumsub needs the applicant for a step of its own (a liveness check), the collection terminal hands its session to Sumsub's SDK after the submit. Add a selfie or proof-of-address field and a second verification step. Use a list_lookup against a tenant list instead of the comparison on countries, so operations can change it without a new version of the rule. Tighten the bands if an inconclusive document should reject rather than review.
Run it
bash
pnpm policy:import examples/policies/individual-kyc-with-documents --publishThe fixture (expect.json) submits Jane Doe from GB with the pack's passport-valid.png, a synthetic passport. The mock verifier decides from the file's header and name: a PNG whose name carries no marker is a valid document of the applicant, so the name matches, the verifier approves, every rule passes, no case opens and the run completes with an automated approve. A copy named passport-expired.png is declined and one named passport-blank.png is inconclusive, which shows the other two outcomes. The check uploads the file as the applicant exactly as the collection terminal would, and posts the signed webhook the mock vendor would send: a catalogue mock cannot post its own. The worker needs STORAGE_* for this pack.