> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-docs-kyc-kyb-verification-options.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# KYC & KYB verification

> Verify individual and business customers through a hosted link or directly through the API — options, required fields, status transitions, and webhooks

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 theme={null}
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 theme={null}
{
  "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">
  When Grid runs KYB for a business customer, the following information and documents are collected before onboarding completes. Use this list to plan what to gather from the business — provide it via `POST /customers`, `POST /beneficial-owners`, and `POST /documents` (or through the hosted flow).

  #### Business identifying information

  * Entity full legal name
  * Doing Business As (DBA) name, if applicable
  * Physical address — the principal place of business, local office, or other physical location of the entity opening the account
  * Countries of operation
  * Identification number — U.S. taxpayer identification number, or, for a foreign business without one, alternative government-issued documentation certifying the existence of the business

  #### Ownership and control structure

  Collected for:

  * **One control person** — a single individual with significant responsibility to control, manage, or direct the legal entity, **and**
  * **All beneficial owners** — every individual who owns 25% or more of the legal entity, directly or indirectly.

  For every such individual, provide:

  * Full name
  * Date of birth
  * Address
  * Identification number, by residency:
    * **U.S. persons** — Social Security Number (SSN) or Individual Taxpayer Identification Number (ITIN)
    * **Non-U.S. persons** — one or more of: ITIN, passport number with country of issuance, alien identification card number, or another government-issued document evidencing nationality or residence and bearing a photograph or similar safeguard

  #### Required documents

  * Company formation and existence documents. For example:
    * Certificate of incorporation
    * Articles of association
  * Proof of ownership and control structure. For example:
    * Corporate organization and ownership chart
    * Shareholder agreements
    * Operating agreements
    * Register of members
    * Certification of controlling person and beneficial owners
  * Proof of address, dated within the last 3 months. For example:
    * Utility bill
    * Bank statement
    * Lease agreement
    * Official correspondence
  * Tax ID or equivalent identifying-number documents
  * For non-U.S. beneficial owners — passport plus one additional government-issued ID. For example:
    * National ID
</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 theme={null}
{
  "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

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`.

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).
