Skip to content

Merchant KYB onboarding ​

The development seed as a policy pack: the kyb-onboarding workflow, the rule set it scores with and every rule that set names, the kyb-basic collection flow and the blocked_countries list. It is the policy the first-decision tutorial walks through, in a file. Pack: examples/policies/merchant-kyb-onboarding/.

This pack is generated from packages/db/src/seed-data.ts by pnpm policy:pack:seed (scripts/policy-pack-from-seed.mjs) and a test (packages/db/src/seed-pack.test.ts) fails when the two drift. Unlike the other packs it deliberately shares the seed's keys: importing it into the seeded development tenant re-creates the seeded definitions as new draft versions (version 2 of each), and --publish makes those the live ones. Into an empty tenant it is the quickest way to get the tutorial's policy.

A pattern for expressing an onboarding policy, not compliance guidance: the supported countries, weights and bands are the seed's illustrative values. The pack screens with the deterministic mock-sanctions app and verifies documents with doc-verify-mock, both of which the development seed installs; a tenant without those installs can import the pack, but its runs fail on unknown_app until both are installed and enabled.

The decision it automates ​

Whether a business applying for an account is approved, rejected or handed to a reviewer, from what the applicant enters, a sanctions screen of the legal name and, when the owner uploads an identity document, the verifier's verdict.

Data expected ​

  • A subject of kind merchant with data.country (the country the business was registered with the platform under).
  • The kyb-basic collection flow: business details (legalName, country, entityType, website), the beneficial owner for companies (uboName, uboOwnershipPct, uboCountry; sole traders skip the step), a confirmation and an optional identityDocument file.

Steps and rules ​

StepTypeWhy
collectwait_for_collectionThe run pauses until the applicant submits kyb-basic; the data lands under submission.*.
screencall_appmock-sanctions screen on submission.legalName and submission.country; output under sanctions.
has_documentbranchOnly when submission.identityDocument.fileId exists does the run pay for a verification.
idvcall_appdoc-verify-mock verify on the uploaded file and the owner's name; asynchronous: the run waits for the vendor's signed webhook.
rulesevaluate_rulesThe kyb-onboarding rule set.
routebranchrules.outcome == manual_review opens a case; anything else decides at once.
reviewcreate_casekyb_review, priority high.
decideemit_decisionFrom the rules, or from the reviewer's decision when there was a case.

The rule set kyb-onboarding sums weights and maps the score through bands 0-40 approve, 40-90 manual_review, 90-100 reject:

RuleTypeSeverity, weightFails when
country_allowedcomparisonblock, 100subject.country is not US, GB or DE
submitted_country_allowedcomparisonwarn, 50the country entered in the flow is not US, GB or DE
country_blocklistedlist_lookupblock, 100the entered country is on blocked_countries (seeded with KP and IR)
sanctions_hitappwarn, 50the mock screen reports hit (the seeded install blocklists Evil Corp)
high_ownership_foreign_uboexpressionwarn, 30an owner holds 75 % or more from a country other than the business (hasPath guards sole traders)
identity_not_verifiedexpressionwarn, 40a document was verified and the verdict is not approved; passes when no document was uploaded

With these weights a blocklisted country (100 + 50) clamps to 100 and rejects; a sanctions hit plus a foreign owner (80) lands in review; an unverified document alone (40) lands in review.

What a reviewer sees ​

A kyb_review case with the submission, the sanctions matches, the verifier's checks when a document was uploaded, and every rule result with its score. The reviewer's decision becomes the run's decision (source: manual), with the rule results attached.

How to adapt it ​

Change the country lists and the bands in the rule set. Screen with the opensanctions app of the App catalogue instead of the mock, once its apiKey secret is stored: the screen step and the sanctions_hit rule name it, with input: { "type": "Company" } (the seed variant SEED_SANCTIONS_APP=opensanctions shows the exact edits). Verify documents with the sumsub app instead of doc-verify-mock, once its three secrets are stored: its verify takes the same documentId and the applicant as firstName, lastName and dateOfBirth, so map the owner's name into those. Add fields to kyb-basic and rules that read them under submission.*.

Run it ​

bash
pnpm policy:import examples/policies/merchant-kyb-onboarding --publish

The fixture (expect.json) submits Evil Corp from GB with a US owner holding 100 %: sanctions hit (50) and foreign owner (30) score 80, a kyb_review case opens, the check approves it and the run completes with a manual approve.

Released under the Apache-2.0 License.