-
Notifications
You must be signed in to change notification settings - Fork 10
docs(kyc): add a KYC/KYB verification page covering both onboarding paths #831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JasonCWang
wants to merge
1
commit into
main
Choose a base branch
from
docs/kyc-kyb-verification-options
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| title: "KYC & KYB verification" | ||
| icon: "/images/icons/suitcase-work.svg" | ||
| description: "Verify individual and business customers through a hosted link or directly through the API — options, required fields, status transitions, and webhooks" | ||
| "og:image": "/images/og/og-api-reference.png" | ||
| --- | ||
|
|
||
| import VerificationOptions from '/snippets/kyc/verification-options.mdx'; | ||
|
|
||
| <VerificationOptions /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| Business customers are always created with `kybStatus: UNVERIFIED`. The **last 3 characters** of `businessInfo.registrationNumber` decide what happens when the business is verified — whether you submit with `POST /verifications` or send the business through a hosted KYB link: | ||
|
|
||
| | Suffix | Outcome | | ||
| |--------|---------| | ||
| | **003** | No auto-decision. Document and UBO verification run for real — the hosted flow asks for company documents and beneficial owners, and `POST /verifications` applies normal validation (`RESOLVE_ERRORS` until the data is complete). **Use this to test the link flow.** | | ||
| | **001** | Same as `003` — no auto-decision, normal validation applies | | ||
| | **002** | Immediate `kybStatus: REJECTED` (`verificationStatus: REJECTED`), skipping data and document validation | | ||
| | **Any other** | Immediate `kybStatus: APPROVED` (`verificationStatus: APPROVED`), skipping data and document validation | | ||
|
|
||
| <Warning> | ||
| A registration number that doesn't end in `001`, `002`, or `003` is **auto-approved on the spot**. The hosted flow then has nothing left to verify, so it asks for no documents and no beneficial owners — which looks exactly like a broken KYB flow but isn't. Always use a `003` suffix when you want to exercise document and UBO collection. | ||
| </Warning> | ||
|
|
||
| Once a business customer is approved or rejected, further `POST /verifications` calls return `400`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| import KybDataRequirements from '/snippets/kyc/kyb-data-requirements.mdx' | ||
| import KybSandboxSuffixes from '/snippets/kyc/kyb-sandbox-suffixes.mdx' | ||
|
|
||
| Grid verifies individual customers with KYC and business customers with KYB. Which applies is decided by `customerType`, and the result lands on `kycStatus` for individuals and `kybStatus` for businesses. | ||
|
|
||
| **Regulated platforms** run verification through their own compliance systems and create customers directly with `POST /customers`. **Unregulated platforms** have Grid verify, using either of the two paths below. | ||
|
|
||
| ## Your options | ||
|
|
||
| Both paths cover KYC and KYB, produce the same status transitions, and emit the same webhooks. You can mix them — supply what you already hold through the API, then let the hosted flow collect the rest. | ||
|
|
||
| | | Hosted link | Direct API | | ||
| |---|---|---| | ||
| | Who collects the data | Grid's hosted flow, or the provider SDK embedded in your UI | You, in your own UI | | ||
| | Identity documents | Uploaded by the customer in the flow | `POST /documents` | | ||
| | Beneficial owners (KYB) | Declared by the applicant in the flow | `POST /beneficial-owners` | | ||
| | Submission | Automatic when the customer finishes | `POST /verifications` | | ||
| | Resolving missing data | The customer, inside the flow | You, from the returned `errors` array | | ||
| | Best when | You want Grid to own the collection UX | You already collect this data, or need it in your own UI | | ||
|
|
||
| ## Hosted link | ||
|
|
||
| Create the customer, then call `POST /customers/{customerId}/kyc-link`. There's no dedicated KYB link endpoint — this one serves both types, and `customerType` selects which flow the provider runs. | ||
|
|
||
| ```bash | ||
| curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers/Customer:019542f5-b3e7-1d02-0000-000000000001/kyc-link" \ | ||
| -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Idempotency-Key: $(uuidgen)" \ | ||
| -d '{ "redirectUri": "https://yourapp.com/onboarding-complete" }' | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "kycUrl": "https://kyc.lightspark.com/onboard/abc123def456", | ||
| "expiresAt": "2027-01-15T14:32:00Z", | ||
| "provider": "SUMSUB", | ||
| "token": "_act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | ||
| } | ||
| ``` | ||
|
|
||
| Send the customer to `kycUrl`, or pass `token` to the provider's SDK to embed the flow in your own UI. The field is named `kycUrl` for both customer types; for a `BUSINESS` customer it opens the KYB flow, where the applicant confirms the company details, uploads the formation and ownership documents, and declares the control person and every beneficial owner holding 25% or more. | ||
|
|
||
| <Accordion title="What the KYB flow collects"> | ||
| <KybDataRequirements /> | ||
| </Accordion> | ||
|
|
||
| - Links are single-use and expire at `expiresAt`. Each call mints a fresh one; earlier links aren't invalidated. `redirectUri` is optional and must be `https://`. | ||
| - Generating a link doesn't change the customer's status — that happens when they submit. | ||
| - A `409` means contact verification is incomplete. When the customer carries a `contactVerification` object, every channel it lists must reach `VERIFIED` first, via `POST /customers/{customerId}/verify-email` / `verify-phone` and their `/confirm` sub-routes. When the object is absent, nothing is required. | ||
| - Business information you supply via `POST /customers` and `PATCH /customers/{customerId}` is prefilled into the flow, so send as much as you have **before** generating the link. | ||
|
|
||
| <Warning> | ||
| Reaching your `redirectUri` means the customer finished the flow, not that they were approved. Wait for the decision. | ||
| </Warning> | ||
|
|
||
| ## Direct API | ||
|
|
||
| Submit the data yourself: `POST /customers`, then `POST /beneficial-owners` for business customers, `POST /documents` for identity and company documents, and finally `POST /verifications`. If anything is missing, `verificationStatus` comes back as `RESOLVE_ERRORS` with one `errors` entry per problem — fix them and resubmit. | ||
|
|
||
| For the full walkthrough, see [Configuring customers](/payouts-and-b2b/onboarding/configuring-customers). | ||
|
|
||
| ## Creating a business customer | ||
|
|
||
| Either path starts here. `POST /customers` with `customerType: BUSINESS` requires: | ||
|
|
||
| | Field | Notes | | ||
| |-------|-------| | ||
| | `businessInfo.legalName` | Full legal entity name | | ||
| | `businessInfo.country` | Country of incorporation, ISO 3166-1 alpha-2. Sets the applicant's jurisdiction and the tax-ID format validated against | | ||
| | `businessInfo.taxId` | Validated against `businessInfo.country` | | ||
| | `businessInfo.incorporatedOn` | `YYYY-MM-DD` | | ||
|
|
||
| Everything else is optional to the schema, but three groups matter in practice: | ||
|
|
||
| - **Contact channels** — `email` and/or `phoneNumber`, plus `businessInfo.primaryContactFirstName` and `primaryContactLastName`, are required in regions that verify a named representative before verification begins (for example the EU). | ||
| - **Currency-driven fields** — `GET /config` returns `supportedCurrencies`, each with a `providerRequiredCustomerFields` list. Anything listed for a currency the business will use must be supplied. | ||
| - **Everything the review needs** — `address`, `registrationNumber`, `entityType`, `countriesOfOperation`, `businessType`, `purposeOfAccount`, `sourceOfFunds`, and the expected-activity fields aren't enforced at creation. Whatever you omit, the applicant is asked for in the hosted flow. | ||
|
|
||
| Individual customers need `customerType: INDIVIDUAL`; `fullName` must contain both a given and a family name. | ||
|
|
||
| ## Status transitions | ||
|
|
||
| `kycStatus` and `kybStatus` share the same values and the same path: `UNVERIFIED` → `PENDING` → `APPROVED` / `REJECTED`. | ||
|
|
||
| | Status | Meaning | | ||
| |--------|---------| | ||
| | `UNVERIFIED` | Created, not yet submitted. Generating a link does not move it off this value | | ||
| | `PENDING` | Submitted; review under way | | ||
| | `APPROVED` | Passed — unlock funding and money movement | | ||
| | `REJECTED` | Failed | | ||
| | `HOLD` | On hold; the customer may be asked to supply more information | | ||
|
|
||
| While the status is `PENDING`, let the customer finish account setup but block funding and money movement. | ||
|
|
||
| `GET /verifications?customerId=...` gives the finer-grained `verificationStatus` (`RESOLVE_ERRORS`, `IN_PROGRESS`, `PENDING_MANUAL_REVIEW`, `APPROVED`, `REJECTED`, `READY_FOR_VERIFICATION`) and the `errors` array. That detail drives the direct API path; in the hosted flow the customer resolves it inside the flow, so integrate against `kycStatus` / `kybStatus`. | ||
|
|
||
| ## Webhooks | ||
|
|
||
| | Event | Fires when | | ||
| |-------|-----------| | ||
| | `CUSTOMER.KYC_PENDING` / `CUSTOMER.KYB_PENDING` | Submitted for review — use it to show an "under review" state | | ||
| | `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYB_APPROVED` | Terminal: passed | | ||
| | `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYB_REJECTED` | Terminal: failed | | ||
|
|
||
| The `KYB_*` events fire only for `customerType: BUSINESS`. `data` is the full customer resource, identical to `GET /customers/{customerId}`: | ||
|
|
||
| ```json | ||
| { | ||
| "id": "Webhook:019542f5-b3e7-1d02-0000-000000000007", | ||
| "type": "CUSTOMER.KYB_APPROVED", | ||
| "timestamp": "2025-08-15T14:32:00Z", | ||
| "data": { | ||
| "id": "Customer:019542f5-b3e7-1d02-0000-000000000001", | ||
| "customerType": "BUSINESS", | ||
| "kybStatus": "APPROVED" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Verify `X-Grid-Signature` against the raw request body and deduplicate on the webhook `id`. The `VERIFICATION.*` events carry the verification-level detail if you want it. To poll instead, read the status off `GET /customers/{customerId}`. | ||
|
|
||
| ## Sandbox | ||
|
|
||
| <KybSandboxSuffixes /> | ||
|
|
||
| For individual customers and beneficial owners, the equivalent suffixes live on `fullName` and on each owner's last name — see [Sandbox testing](/api-reference/sandbox-testing). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an unregulated platform has not recorded End User Terms consent, this row incorrectly presents KYC/KYB approval as sufficient to unlock funding and money movement, causing customer-scoped transactions to be rejected despite the documented guidance.
Knowledge Base Used: Customers and KYC
Prompt To Fix With AI