From 0dff7244c877976a646fedde48025570c9221750 Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Mon, 25 May 2026 01:31:29 -0700 Subject: [PATCH 1/4] docs(onboarding): sync KYC/customer examples with schema - POST /beneficial-owners: roles[] array + nested personalInfo (was singular role + flat fullName) - POST /documents: documentHolder + country required (was customerId/beneficialOwnerId form fields) - Webhook types use OBJECT.EVENT dot-notation: CUSTOMER.KYC_APPROVED, INVITATION.CLAIMED (no invented KYC_STATUS, MANUALLY_* or KYC_SUBMITTED values) - KycStatus terminal set: UNVERIFIED|PENDING|APPROVED|REJECTED only - Bulk CSV columns match customers_bulk_csv.yaml (removed bank-account columns - those go via /customers/external-accounts) - Bulk job-status uses id + flat {correlationId, code, message} errors - Business customer PATCH/create uses businessInfo.{legalName, taxId} (was top-level) Co-Authored-By: Claude Opus 4.7 --- .../onboarding/configuring-customers.mdx | 22 +++--- .../global-p2p/onboarding/invitations.mdx | 18 ++--- .../onboarding/configuring-customers.mdx | 67 ++++++------------- mintlify/payouts-and-b2b/quickstart.mdx | 2 +- .../onboarding/configuring-customers.mdx | 8 ++- mintlify/ramps/platform-tools/webhooks.mdx | 20 ++++-- .../onboarding/configuring-customers.mdx | 14 +++- .../snippets/creating-customers/customers.mdx | 36 +++++----- .../creating-customers/onboarding-model.mdx | 2 +- mintlify/snippets/kyc/kyc-unregulated.mdx | 39 +++++++---- mintlify/snippets/kyc/kyc-webhooks.mdx | 25 ++----- 11 files changed, 123 insertions(+), 130 deletions(-) diff --git a/mintlify/global-p2p/onboarding/configuring-customers.mdx b/mintlify/global-p2p/onboarding/configuring-customers.mdx index 02b57c50..492e97d9 100644 --- a/mintlify/global-p2p/onboarding/configuring-customers.mdx +++ b/mintlify/global-p2p/onboarding/configuring-customers.mdx @@ -186,7 +186,7 @@ Response: 3. Redirect the customer to `kycUrl`, or pass the optional `token` to the provider's SDK to embed verification directly in your UI. 4. After the user is redirected back to your app, they can continue with account setup until KYC review is complete. -5. Track the decision via the `KYC_STATUS` webhook, or poll `GET /customers/{customerId}` and inspect `kycStatus`. On `APPROVED`, the customer is ready to transact and you can unlock funding. +5. Track the decision via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` webhook (use the `CUSTOMER.KYB_*` siblings for business customers), or poll `GET /customers/{customerId}` and inspect `kycStatus`. On `APPROVED`, the customer is ready to transact and you can unlock funding. ### Handling KYC/KYB Webhooks @@ -248,9 +248,9 @@ curl -sS -X POST "https://api.lightspark.com/grid/2025-10-13/customers/bulk/csv" The CSV file should follow a specific format with required and optional columns based on customer type. Here's an example: ```csv -umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,accountType,accountNumber,bankName,platformAccountId,businessLegalName,routingNumber,accountCategory -$john.doe@uma.domain.com,cust_user123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US,US_ACCOUNT,123456789,Chase Bank,chase_primary_1234,,222888888,SAVINGS -$acme@uma.domain.com,cust_biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US,US_ACCOUNT,987654321,Bank of America,boa_business_5678,Acme Corp,121212121,CHECKING +umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,businessLegalName +$john.doe@uma.domain.com,cust_user123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US, +$acme@uma.domain.com,cust_biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US,Acme Corp ``` @@ -277,21 +277,19 @@ Example job status response: ```json { - "jobId": "job_123456789", + "id": "Job:019542f5-b3e7-1d02-0000-000000000006", "status": "PROCESSING", "progress": { "total": 5000, "processed": 2500, - "successful": 2499, - "failed": 1 + "successful": 2450, + "failed": 50 }, "errors": [ { - "platformCustomerId": "cust_biz456", - "error": { - "code": "validation_error", - "message": "Invalid bank account number" - } + "correlationId": "cust_biz456", + "code": "INVALID_FIELD", + "message": "Invalid bank account number" } ] } diff --git a/mintlify/global-p2p/onboarding/invitations.mdx b/mintlify/global-p2p/onboarding/invitations.mdx index cd8e2d95..755dbb58 100644 --- a/mintlify/global-p2p/onboarding/invitations.mdx +++ b/mintlify/global-p2p/onboarding/invitations.mdx @@ -98,7 +98,7 @@ they claim it. The response will include the payment amount in the invitation de } ``` -When the invitee claims the invitation, your platform will receive an `INVITATION_CLAIMED` webhook. At this point, you should: +When the invitee claims the invitation, your platform will receive an `INVITATION.CLAIMED` webhook. At this point, you should: 1. Check the `amountToSend` field in the webhook payload 2. Create a quote for the payment amount (sender-locked) @@ -106,7 +106,7 @@ When the invitee claims the invitation, your platform will receive an `INVITATIO Note that the actual sending of the payment must be done by your platform after receiving the webhook. If your platform either does not send the payment or the payment fails, the invitee will not receive the amount. The `amountToSend` field is primarily used for display purposes on the claiming side of the invitation. -These payments can only be sender-locked, meaning that the sender will not know ahead of time how much the receiver will receive in their local currency. The exchange rate will be determined at the time the payment is executed. If you'd like, you can also send a push notification to your sending user when you receive the `INVITATION_CLAIMED` webhook and have them approve the payment interactively instead. +These payments can only be sender-locked, meaning that the sender will not know ahead of time how much the receiver will receive in their local currency. The exchange rate will be determined at the time the payment is executed. If you'd like, you can also send a push notification to your sending user when you receive the `INVITATION.CLAIMED` webhook and have them approve the payment interactively instead. ### Best practices @@ -132,7 +132,7 @@ A successful claim will: 1. Associate the invitee's UMA address with the invitation 2. Change the invitation status from `PENDING` to `CLAIMED` -3. Trigger an `INVITATION_CLAIMED` webhook to notify the inviter's platform of the claim +3. Trigger an `INVITATION.CLAIMED` webhook to notify the inviter's platform of the claim ## Cancelling Invitations @@ -177,7 +177,7 @@ You can check the status of an invitation at any time by making a GET request to ## Webhook Integration -When an invitation is claimed, the Grid API will send an `INVITATION_CLAIMED` webhook to your configured webhook endpoint. This allows you to: +When an invitation is claimed, the Grid API will send an `INVITATION.CLAIMED` webhook to your configured webhook endpoint. This allows you to: - Track invitation usage and conversion rates - Apply referral bonuses or rewards to the inviter @@ -187,7 +187,10 @@ Example webhook payload: ```json { - "invitation": { + "id": "Webhook:019542f5-b3e7-1d02-0000-000000000008", + "type": "INVITATION.CLAIMED", + "timestamp": "2023-09-01T15:45:00Z", + "data": { "code": "019542f5", "createdAt": "2023-09-01T14:30:00Z", "claimedAt": "2023-09-01T15:45:00Z", @@ -195,10 +198,7 @@ Example webhook payload: "inviteeUma": "$invitee@uma.domain", "url": "https://uma.me/i/019542f5", "status": "CLAIMED" - }, - "timestamp": "2023-09-01T15:45:00Z", - "webhookId": "Webhook:019542f5-b3e7-1d02-0000-000000000008", - "type": "INVITATION_CLAIMED" + } } ``` diff --git a/mintlify/payouts-and-b2b/onboarding/configuring-customers.mdx b/mintlify/payouts-and-b2b/onboarding/configuring-customers.mdx index a3770d22..bbbd1d37 100644 --- a/mintlify/payouts-and-b2b/onboarding/configuring-customers.mdx +++ b/mintlify/payouts-and-b2b/onboarding/configuring-customers.mdx @@ -62,16 +62,19 @@ curl -X PATCH "https://api.lightspark.com/grid/2025-10-13/customers/{customerId} -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{ - "bankAccountInfo": { - "accountType": "US_ACCOUNT", - "accountNumber": "123456789", - "routingNumber": "987654321", - "bankName": "Chase Bank" + "customerType": "INDIVIDUAL", + "currencies": ["USD", "EUR", "USDC"], + "address": { + "line1": "456 Market St", + "city": "San Francisco", + "state": "CA", + "postalCode": "94103", + "country": "US" } }' ``` -Note that not all customer information can be updated. Particularly for non-regulated platforms, you cannot update personal information after the customer has been created. +Note that not all customer information can be updated. Particularly for non-regulated platforms, you cannot update personal information after the customer has been created. The `customerType` discriminator is required on PATCH so the API knows which set of optional fields to validate. To update bank-account details, use the `/customers/external-accounts` endpoints — bank accounts are no longer managed inline on the customer resource. ## Bank Account Information @@ -212,9 +215,9 @@ curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers/bulk/csv" \ The CSV file should follow a specific format with required and optional columns based on customer type. Here's an example: ```csv -platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,accountType,accountNumber,bankName,platformAccountId,businessLegalName,routingNumber,accountCategory -customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US,US_ACCOUNT,123456789,Chase Bank,chase_primary_1234,,222888888,SAVINGS -biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US,US_ACCOUNT,987654321,Bank of America,boa_business_5678,Acme Corp,121212121,CHECKING +umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,businessLegalName +$john.doe@uma.domain.com,customer123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US, +$acme@uma.domain.com,biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US,Acme Corp ``` @@ -241,21 +244,19 @@ Example job status response: ```json { - "jobId": "job_123456789", + "id": "Job:019542f5-b3e7-1d02-0000-000000000006", "status": "PROCESSING", "progress": { "total": 5000, "processed": 2500, - "successful": 2499, - "failed": 1 + "successful": 2450, + "failed": 50 }, "errors": [ { - "platformCustomerId": "biz456", - "error": { - "code": "validation_error", - "message": "Invalid bank account number" - } + "correlationId": "biz456", + "code": "INVALID_FIELD", + "message": "Invalid bank account number" } ] } @@ -277,6 +278,7 @@ The CSV file should have the following columns: Required columns for all customers: +- umaAddress: The customer's UMA address (e.g., `$john.doe@uma.domain.com`) - platformCustomerId: Your platform's unique identifier for the customer - customerType: Either "INDIVIDUAL" or "BUSINESS" @@ -289,9 +291,6 @@ Required columns for individual customers: - state: State/Province/Region - postalCode: Postal/ZIP code - country: Country code (ISO 3166-1 alpha-2) -- accountType: Bank account type (CLABE, US_ACCOUNT, PIX, IBAN, UPI) -- accountNumber: Bank account number -- bankName: Name of the bank Required columns for business customers: @@ -301,9 +300,6 @@ Required columns for business customers: - state: State/Province/Region - postalCode: Postal/ZIP code - country: Country code (ISO 3166-1 alpha-2) -- accountType: Bank account type (CLABE, US_ACCOUNT, PIX, IBAN, UPI) -- accountNumber: Bank account number -- bankName: Name of the bank Optional columns for all customers: @@ -320,28 +316,5 @@ Optional columns for business customers: - businessRegistrationNumber: Business registration number - businessTaxId: Tax identification number -Additional required columns based on account type: - -For US_ACCOUNT: - -- routingNumber: ACH routing number (9 digits) -- accountCategory: Either "CHECKING" or "SAVINGS" - -For CLABE: - -- clabeNumber: 18-digit CLABE number - -For PIX: - -- pixKey: PIX key value -- pixKeyType: Type of PIX key (CPF, CNPJ, EMAIL, PHONE, RANDOM) - -For UPI: - -- vpa: Virtual Payment Address for UPI payments - -For IBAN: - -- iban: International Bank Account Number -- swiftBic: SWIFT/BIC code (8 or 11 characters) +Bank-account details are no longer part of the bulk customer CSV — manage them separately via the `/customers/external-accounts` endpoints. diff --git a/mintlify/payouts-and-b2b/quickstart.mdx b/mintlify/payouts-and-b2b/quickstart.mdx index b928ced4..19a81cb0 100644 --- a/mintlify/payouts-and-b2b/quickstart.mdx +++ b/mintlify/payouts-and-b2b/quickstart.mdx @@ -112,7 +112,7 @@ The hosted interface handles document collection, verification checks, and compl ### Track the decision -Reaching the `redirectUri` only means the customer **finished the hosted flow** — not that they were approved. Wait for the final decision via the `KYC_STATUS` webhook (recommended) or by polling `GET /customers/{customerId}` and inspecting `kycStatus`. On `APPROVED`, the customer is ready to transact and you can unlock funding. +Reaching the `redirectUri` only means the customer **finished the hosted flow** — not that they were approved. Wait for the final decision via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` webhook (use the `CUSTOMER.KYB_*` siblings for business customers) — recommended — or by polling `GET /customers/{customerId}` and inspecting `kycStatus`. On `APPROVED`, the customer is ready to transact and you can unlock funding. ## Get the Customer's Internal Account diff --git a/mintlify/ramps/onboarding/configuring-customers.mdx b/mintlify/ramps/onboarding/configuring-customers.mdx index f058981c..6d8916f8 100644 --- a/mintlify/ramps/onboarding/configuring-customers.mdx +++ b/mintlify/ramps/onboarding/configuring-customers.mdx @@ -59,7 +59,7 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers' \ For corporate conversions and business accounts. -**Required fields:** `businessName`, `email`, `taxId`, `address` +**Required fields:** `customerType`. Most providers also require `businessInfo.legalName` and `address` for KYB. ```bash curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers' \ @@ -68,9 +68,11 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers' \ -d '{ "platformCustomerId": "biz_67890", "customerType": "BUSINESS", - "businessName": "Acme Corporation", "email": "finance@acme.com", - "taxId": "12-3456789", + "businessInfo": { + "legalName": "Acme Corporation", + "taxId": "12-3456789" + }, "address": {...} }' ``` diff --git a/mintlify/ramps/platform-tools/webhooks.mdx b/mintlify/ramps/platform-tools/webhooks.mdx index fcf0dd58..c851417e 100644 --- a/mintlify/ramps/platform-tools/webhooks.mdx +++ b/mintlify/ramps/platform-tools/webhooks.mdx @@ -105,17 +105,23 @@ Grid sends webhooks for key events in the ramp lifecycle: - - Sent when customer KYC verification completes (required before conversions). + + Sent when customer KYC verification completes (required before conversions). Business customers emit the `CUSTOMER.KYB_*` siblings. ```json { - "webhookId": "Webhook:019542f5-b3e7-1d02-0000-000000000020", - "type": "KYC_STATUS", + "id": "Webhook:019542f5-b3e7-1d02-0000-000000000020", + "type": "CUSTOMER.KYC_APPROVED", "timestamp": "2025-10-03T14:32:00Z", - "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", - "kycStatus": "APPROVED", - "platformCustomerId": "user_12345" + "data": { + "id": "Customer:019542f5-b3e7-1d02-0000-000000000001", + "platformCustomerId": "user_12345", + "customerType": "INDIVIDUAL", + "kycStatus": "APPROVED", + "fullName": "Jane Doe", + "createdAt": "2025-07-21T17:32:28Z", + "updatedAt": "2025-10-03T14:32:00Z" + } } ``` diff --git a/mintlify/rewards/onboarding/configuring-customers.mdx b/mintlify/rewards/onboarding/configuring-customers.mdx index 65530112..a39b6807 100644 --- a/mintlify/rewards/onboarding/configuring-customers.mdx +++ b/mintlify/rewards/onboarding/configuring-customers.mdx @@ -40,7 +40,7 @@ The `platformCustomerId` field is optional but recommended. Use your existing us When a customer is created successfully, internal accounts are automatically created for each currency configured on your platform. These accounts can be used as sources or destinations for transfers. -### Hanlding KYC/KYC Webhooks +### Handling KYC/KYB Webhooks @@ -79,6 +79,18 @@ Filter customers updated after this timestamp (ISO 8601) Filter customers updated before this timestamp (ISO 8601) + +Filter by customer region (ISO 3166-1 alpha-2 country code) + + + +Filter by currency code. Returns customers that have this currency in their enabled currencies list. + + + +Filter by UMA address + + Whether to include deleted customers in results (default: false) diff --git a/mintlify/snippets/creating-customers/customers.mdx b/mintlify/snippets/creating-customers/customers.mdx index 6ff90298..7c584e2e 100644 --- a/mintlify/snippets/creating-customers/customers.mdx +++ b/mintlify/snippets/creating-customers/customers.mdx @@ -8,7 +8,7 @@ export const Customers = ({ individualEnabled = true, businessEnabled = true, um There are two platform models for regulated and unregulated platforms. - Regulated institutions: Use your existing compliance processes. Create individual and business customers directly via `POST /customers`. The information you supply is used for beneficiary/counterparty compliance screening. - - Unregulated institutions: Grid performs KYC (individuals) and KYB (businesses). Choose either the **hosted link flow** (redirect or embed Grid's provider for verification) or **direct API onboarding** — for `INDIVIDUAL` customers, submit personal information through `POST /customers`; for `BUSINESS` customers, also register beneficial owners via `POST /beneficial-owners`. Use `POST /documents` for supporting documents and `POST /verifications` to submit for review. Both paths produce the same `kycStatus` transitions and emit the same `KYC_STATUS` webhooks. While verification is pending, allow customers to finish account setup but block funding and money movement. + - Unregulated institutions: Grid performs KYC (individuals) and KYB (businesses). Choose either the **hosted link flow** (redirect or embed Grid's provider for verification) or **direct API onboarding** — for `INDIVIDUAL` customers, submit personal information through `POST /customers`; for `BUSINESS` customers, also register beneficial owners via `POST /beneficial-owners`. Use `POST /documents` for supporting documents and `POST /verifications` to submit for review. Both paths produce the same `kycStatus` transitions and emit the same `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYC_PENDING` (and `CUSTOMER.KYB_*` equivalents) webhooks. While verification is pending, allow customers to finish account setup but block funding and money movement. ## Customer Types @@ -174,21 +174,23 @@ Response: 3. Redirect the customer to `kycUrl`, or pass the optional `token` to the provider's SDK to embed verification directly in your UI. 4. After the customer is redirected back to your app, they can continue with account setup until KYC review is complete. -5. Track the decision via the `KYC_STATUS` webhook, or poll `GET /customers/{customerId}` and inspect `kycStatus`. On `APPROVED`, the customer is ready to transact and you can unlock funding. +5. Track the decision via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYC_PENDING` webhooks (use the `CUSTOMER.KYB_*` siblings for business customers), or poll `GET /customers/{customerId}` and inspect `kycStatus`. On `APPROVED`, the customer is ready to transact and you can unlock funding. Webhook example: ```json {{ - "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", - "kycStatus": "APPROVED", - "type": "KYC_STATUS", + "id": "Webhook:019542f5-b3e7-1d02-0000-000000000007", + "type": "CUSTOMER.KYC_APPROVED", "timestamp": "2025-01-15T14:32:00Z", - "webhookId": "Webhook:019542f5-b3e7-1d02-0000-000000000007" + "data": { + "id": "Customer:019542f5-b3e7-1d02-0000-000000000001", + "kycStatus": "APPROVED" + } }} ``` -Possible final statuses include APPROVED, REJECTED, EXPIRED, CANCELED, MANUALLY_APPROVED, and MANUALLY_REJECTED. Verify `X-Grid-Signature` against the raw request body using the dashboard public key. +Terminal `kycStatus` values are `APPROVED` and `REJECTED`. Verify `X-Grid-Signature` against the raw request body using the dashboard public key. @@ -267,9 +269,9 @@ curl -sS -X POST "https://api.lightspark.com/grid/2025-10-13/customers/bulk/csv" The CSV file should follow a specific format with required and optional columns based on customer type. Here's an example: ```csv -umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,accountType,accountNumber,bankName,platformAccountId,businessLegalName,routingNumber,accountCategory\ -$john.doe@uma.domain.com,cust_user123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US,US_ACCOUNT,123456789,Chase Bank,chase_primary_1234,,222888888,SAVINGS -$acme@uma.domain.com,cust_biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US,US_ACCOUNT,987654321,Bank of America,boa_business_5678,Acme Corp,121212121,CHECKING +umaAddress,platformCustomerId,customerType,fullName,birthDate,addressLine1,city,state,postalCode,country,businessLegalName +$john.doe@uma.domain.com,cust_user123,INDIVIDUAL,John Doe,1990-01-15,123 Main St,San Francisco,CA,94105,US, +$acme@uma.domain.com,cust_biz456,BUSINESS,,,400 Commerce Way,Austin,TX,78701,US,Acme Corp ``` @@ -296,21 +298,19 @@ Example job status response: ```json {{ - "jobId": "job_123456789", + "id": "Job:019542f5-b3e7-1d02-0000-000000000006", "status": "PROCESSING", "progress": { "total": 5000, "processed": 2500, - "successful": 2499, - "failed": 1 + "successful": 2450, + "failed": 50 }, "errors": [ { - "platformCustomerId": "cust_biz456", - "error": { - "code": "validation_error", - "message": "Invalid bank account number" - } + "correlationId": "cust_biz456", + "code": "INVALID_FIELD", + "message": "Invalid bank account number" } ] }} diff --git a/mintlify/snippets/creating-customers/onboarding-model.mdx b/mintlify/snippets/creating-customers/onboarding-model.mdx index 5beba625..1b2619dd 100644 --- a/mintlify/snippets/creating-customers/onboarding-model.mdx +++ b/mintlify/snippets/creating-customers/onboarding-model.mdx @@ -3,4 +3,4 @@ There are two models for regulated and unregulated platforms. - Regulated platforms: Use your existing compliance processes. Create individual and business customers directly via `POST /customers`. The information you supply is used for beneficiary/counterparty compliance screening. -- Unregulated platforms: Grid performs KYC (individuals) and KYB (businesses). Choose either the **hosted link flow** (redirect or embed Grid's provider for verification) or **direct API onboarding** — for `INDIVIDUAL` customers, submit personal information through `POST /customers`; for `BUSINESS` customers, also register beneficial owners via `POST /beneficial-owners`. Both paths produce the same `kycStatus` transitions and emit the same `KYC_STATUS` webhooks. While verification is pending, allow customers to finish account setup but block funding and money movement. \ No newline at end of file +- Unregulated platforms: Grid performs KYC (individuals) and KYB (businesses). Choose either the **hosted link flow** (redirect or embed Grid's provider for verification) or **direct API onboarding** — for `INDIVIDUAL` customers, submit personal information through `POST /customers`; for `BUSINESS` customers, also register beneficial owners via `POST /beneficial-owners`. Both paths produce the same `kycStatus` transitions and emit the same `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYC_PENDING` (and `CUSTOMER.KYB_*` equivalents) webhooks. While verification is pending, allow customers to finish account setup but block funding and money movement. \ No newline at end of file diff --git a/mintlify/snippets/kyc/kyc-unregulated.mdx b/mintlify/snippets/kyc/kyc-unregulated.mdx index 976f98be..d57e4abf 100644 --- a/mintlify/snippets/kyc/kyc-unregulated.mdx +++ b/mintlify/snippets/kyc/kyc-unregulated.mdx @@ -1,5 +1,5 @@ -**Unregulated platforms** rely on Grid to run KYC for individuals and KYB for businesses. You can onboard customers either through the **hosted KYC/KYB link flow** below, or by **submitting customer data directly through the API**. Both paths produce the same `kycStatus` transitions and emit the same `KYC_STATUS` webhooks. +**Unregulated platforms** rely on Grid to run KYC for individuals and KYB for businesses. You can onboard customers either through the **hosted KYC/KYB link flow** below, or by **submitting customer data directly through the API**. Both paths produce the same `kycStatus` transitions and emit the same `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYC_PENDING` (and `CUSTOMER.KYB_*` equivalents) webhooks. Either path works for unregulated platforms: @@ -86,18 +86,18 @@ The response always includes `kycUrl` for the hosted flow. For providers that su Reaching your `redirectUri` only means the customer **finished the hosted flow** — not that they were approved. Wait for the final decision in one of two ways: - - **Webhook (recommended):** Subscribe to `KYC_STATUS` to be notified when the customer reaches a terminal status (`APPROVED`, `REJECTED`, `EXPIRED`, or `CANCELED`). + - **Webhook (recommended):** Subscribe to `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` (and `CUSTOMER.KYB_APPROVED` / `CUSTOMER.KYB_REJECTED` for business customers) to be notified when the customer reaches a terminal status (`APPROVED` or `REJECTED`). - **Polling:** Call `GET /customers/{customerId}` and inspect `kycStatus`. - On `APPROVED`, the customer is ready to transact — proceed with account setup and unlock funding. On `REJECTED` or `EXPIRED`, surface the appropriate next step (for example, regenerate the link or request manual review). + On `APPROVED`, the customer is ready to transact — proceed with account setup and unlock funding. On `REJECTED`, surface the appropriate next step (for example, regenerate the link or request manual review). ### Direct API Onboarding -Prefer to collect identity information in your own UI and submit it to Grid yourself? Use the API directly instead of redirecting to a hosted link. The customer's `kycStatus` transitions the same way and you receive the same `KYC_STATUS` webhooks. +Prefer to collect identity information in your own UI and submit it to Grid yourself? Use the API directly instead of redirecting to a hosted link. The customer's `kycStatus` transitions the same way and you receive the same `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYC_PENDING` (and `CUSTOMER.KYB_*` equivalents) webhooks. The shape of the flow depends on the customer type: @@ -138,8 +138,9 @@ The shape of the flow depends on the customer type: ```bash curl -X POST "https://api.lightspark.com/grid/2025-10-13/documents" \ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ - -F "customerId=Customer:019542f5-b3e7-1d02-0000-000000000001" \ + -F "documentHolder=Customer:019542f5-b3e7-1d02-0000-000000000001" \ -F "documentType=PASSPORT" \ + -F "country=US" \ -F "file=@./passport.jpg" ``` @@ -189,7 +190,7 @@ The shape of the flow depends on the customer type: - Track terminal `kycStatus` transitions via the `KYC_STATUS` webhook (recommended) or by polling `GET /customers/{customerId}`. On `APPROVED`, unlock funding and money movement. + Track terminal `kycStatus` transitions via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` webhook (recommended) or by polling `GET /customers/{customerId}`. On `APPROVED`, unlock funding and money movement. @@ -231,10 +232,23 @@ The shape of the flow depends on the customer type: -H "Content-Type: application/json" \ -d '{ "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", - "fullName": "Jane Doe", - "birthDate": "1985-06-15", - "role": "DIRECTOR", - "ownershipPercentage": 40 + "roles": ["DIRECTOR"], + "ownershipPercentage": 40, + "personalInfo": { + "firstName": "Jane", + "lastName": "Doe", + "birthDate": "1985-06-15", + "nationality": "US", + "address": { + "line1": "123 Pine Street", + "city": "Seattle", + "state": "WA", + "postalCode": "98101", + "country": "US" + }, + "idType": "SSN", + "identifier": "123-45-6789" + } }' ``` @@ -245,8 +259,9 @@ The shape of the flow depends on the customer type: ```bash curl -X POST "https://api.lightspark.com/grid/2025-10-13/documents" \ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ - -F "beneficialOwnerId=BeneficialOwner:019542f5-b3e7-1d02-0000-00000000abcd" \ + -F "documentHolder=BeneficialOwner:019542f5-b3e7-1d02-0000-00000000abcd" \ -F "documentType=PASSPORT" \ + -F "country=US" \ -F "file=@./owner-passport.jpg" ``` @@ -302,7 +317,7 @@ The shape of the flow depends on the customer type: - Track terminal `kycStatus` transitions via the `KYC_STATUS` webhook (recommended) or by polling `GET /customers/{customerId}`. On `APPROVED`, unlock funding and money movement. + Track terminal `kycStatus` transitions via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` webhook (recommended; use the `CUSTOMER.KYB_*` siblings for business customers) or by polling `GET /customers/{customerId}`. On `APPROVED`, unlock funding and money movement. diff --git a/mintlify/snippets/kyc/kyc-webhooks.mdx b/mintlify/snippets/kyc/kyc-webhooks.mdx index 678edea8..411f3730 100644 --- a/mintlify/snippets/kyc/kyc-webhooks.mdx +++ b/mintlify/snippets/kyc/kyc-webhooks.mdx @@ -44,12 +44,11 @@ Unique identifier for this webhook delivery. Use this for idempotency to prevent -Status-specific event type. KYC webhooks use `CUSTOMER.*` types: -- `CUSTOMER.KYC_APPROVED`: Customer verification completed successfully -- `CUSTOMER.KYC_REJECTED`: Customer verification was rejected -- `CUSTOMER.KYC_SUBMITTED`: KYC verification was initially submitted -- `CUSTOMER.KYC_MANUALLY_APPROVED`: Customer was manually approved by platform -- `CUSTOMER.KYC_MANUALLY_REJECTED`: Customer was manually rejected by platform +Status-specific event type. KYC/KYB webhooks use `CUSTOMER.*` types: +- `CUSTOMER.KYC_APPROVED`: Individual customer verification completed successfully +- `CUSTOMER.KYC_REJECTED`: Individual customer verification was rejected +- `CUSTOMER.KYC_PENDING`: Individual customer is awaiting review +- `CUSTOMER.KYB_APPROVED` / `CUSTOMER.KYB_REJECTED` / `CUSTOMER.KYB_PENDING`: business equivalents (only fires for `customerType: BUSINESS`) @@ -57,7 +56,7 @@ The full customer resource object, same as the corresponding `GET /customers/{id -Intermediate states like `PENDING_REVIEW` do not trigger webhook notifications. Only final resolution states will send webhook notifications. +`CUSTOMER.KYC_PENDING` (or `CUSTOMER.KYB_PENDING`) fires when a customer enters `kycStatus: PENDING` (for example, after they are submitted for review). Final outcomes use `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` (and the `CUSTOMER.KYB_*` siblings for business customers). @@ -80,18 +79,6 @@ app.post('/webhooks/kyc-status', async (req, res) => { await sendRejectionEmail(data.id); break; - case 'CUSTOMER.KYC_MANUALLY_APPROVED': - // Handle manual approval - await activateCustomer(data.id); - await sendWelcomeEmail(data.id); - break; - - case 'CUSTOMER.KYC_MANUALLY_REJECTED': - // Handle manual rejection - await notifySupport(data.id, 'KYC_MANUALLY_REJECTED'); - await sendRejectionEmail(data.id); - break; - default: // Log unexpected types console.log(`Unexpected webhook type ${type} for customer ${data.id}`); From 79d7b2550c67c5eb1258f489da9c1168de65c8e3 Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Mon, 25 May 2026 02:44:34 -0700 Subject: [PATCH 2/4] clarify CUSTOMER.KYC_PENDING as intermediate signal (Greptile review) PENDING fires when a customer is submitted for review; APPROVED and REJECTED are the terminal decisions. The webhook handler example and narrative now distinguish the two roles instead of bundling them. Co-Authored-By: Claude Opus 4.7 --- mintlify/snippets/creating-customers/customers.mdx | 2 +- mintlify/snippets/kyc/kyc-unregulated.mdx | 2 +- mintlify/snippets/kyc/kyc-webhooks.mdx | 11 +++++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mintlify/snippets/creating-customers/customers.mdx b/mintlify/snippets/creating-customers/customers.mdx index 7c584e2e..f20da4bb 100644 --- a/mintlify/snippets/creating-customers/customers.mdx +++ b/mintlify/snippets/creating-customers/customers.mdx @@ -174,7 +174,7 @@ Response: 3. Redirect the customer to `kycUrl`, or pass the optional `token` to the provider's SDK to embed verification directly in your UI. 4. After the customer is redirected back to your app, they can continue with account setup until KYC review is complete. -5. Track the decision via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYC_PENDING` webhooks (use the `CUSTOMER.KYB_*` siblings for business customers), or poll `GET /customers/{customerId}` and inspect `kycStatus`. On `APPROVED`, the customer is ready to transact and you can unlock funding. +5. Track the decision via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` webhooks (use the `CUSTOMER.KYB_*` siblings for business customers), or poll `GET /customers/{customerId}` and inspect `kycStatus`. `CUSTOMER.KYC_PENDING` is an intermediate signal that the customer is under review — subscribe to it if you want to surface that state, but treat only `APPROVED` / `REJECTED` as the decision. On `APPROVED`, the customer is ready to transact and you can unlock funding. Webhook example: ```json diff --git a/mintlify/snippets/kyc/kyc-unregulated.mdx b/mintlify/snippets/kyc/kyc-unregulated.mdx index d57e4abf..94b8e108 100644 --- a/mintlify/snippets/kyc/kyc-unregulated.mdx +++ b/mintlify/snippets/kyc/kyc-unregulated.mdx @@ -86,7 +86,7 @@ The response always includes `kycUrl` for the hosted flow. For providers that su Reaching your `redirectUri` only means the customer **finished the hosted flow** — not that they were approved. Wait for the final decision in one of two ways: - - **Webhook (recommended):** Subscribe to `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` (and `CUSTOMER.KYB_APPROVED` / `CUSTOMER.KYB_REJECTED` for business customers) to be notified when the customer reaches a terminal status (`APPROVED` or `REJECTED`). + - **Webhook (recommended):** Subscribe to `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` (and `CUSTOMER.KYB_APPROVED` / `CUSTOMER.KYB_REJECTED` for business customers) to be notified when the customer reaches a terminal status. `CUSTOMER.KYC_PENDING` (and the `KYB_PENDING` sibling) also fires when the customer is submitted for review — subscribe to it as well if you want to surface an "under review" state to the customer. - **Polling:** Call `GET /customers/{customerId}` and inspect `kycStatus`. diff --git a/mintlify/snippets/kyc/kyc-webhooks.mdx b/mintlify/snippets/kyc/kyc-webhooks.mdx index 411f3730..c387e5db 100644 --- a/mintlify/snippets/kyc/kyc-webhooks.mdx +++ b/mintlify/snippets/kyc/kyc-webhooks.mdx @@ -61,8 +61,10 @@ The full customer resource object, same as the corresponding `GET /customers/{id ```javascript -// Example webhook handler for KYC status updates -// Note: Only final states trigger webhook notifications +// Example webhook handler for KYC status updates. +// CUSTOMER.KYC_APPROVED and CUSTOMER.KYC_REJECTED are terminal decisions; +// CUSTOMER.KYC_PENDING is an intermediate signal that the customer has been +// submitted for review (useful for surfacing "we're still reviewing" UI). app.post('/webhooks/kyc-status', async (req, res) => { const { type, data } = req.body; @@ -79,6 +81,11 @@ app.post('/webhooks/kyc-status', async (req, res) => { await sendRejectionEmail(data.id); break; + case 'CUSTOMER.KYC_PENDING': + // Intermediate: customer submitted for review, no decision yet + await markCustomerUnderReview(data.id); + break; + default: // Log unexpected types console.log(`Unexpected webhook type ${type} for customer ${data.id}`); From 062363411fa43382e332e3bbcee9e63802dff0a8 Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Mon, 25 May 2026 04:33:59 -0700 Subject: [PATCH 3/4] handle KYB_* webhooks in handler and KYB tab (Greptile review) The example webhook handler in kyc-webhooks.mdx now branches on the KYB_APPROVED / KYB_REJECTED / KYB_PENDING events as well, so business customer events aren't silently logged. In kyc-unregulated.mdx, the KYB tab's "Track the decision" step now recommends KYB_APPROVED / KYB_REJECTED (and the KYB_PENDING intermediate) instead of the individual KYC_* siblings, since the KYC_* events don't fire for business customers. Co-Authored-By: Claude Opus 4.7 --- mintlify/snippets/kyc/kyc-unregulated.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mintlify/snippets/kyc/kyc-unregulated.mdx b/mintlify/snippets/kyc/kyc-unregulated.mdx index 94b8e108..d2f0fdd7 100644 --- a/mintlify/snippets/kyc/kyc-unregulated.mdx +++ b/mintlify/snippets/kyc/kyc-unregulated.mdx @@ -190,7 +190,7 @@ The shape of the flow depends on the customer type: - Track terminal `kycStatus` transitions via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` webhook (recommended) or by polling `GET /customers/{customerId}`. On `APPROVED`, unlock funding and money movement. + Track terminal `kycStatus` transitions via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` webhook (recommended) or by polling `GET /customers/{customerId}`. `CUSTOMER.KYC_PENDING` also fires when the customer is submitted for review — subscribe to it if you want to surface an "under review" state. On `APPROVED`, unlock funding and money movement. @@ -317,7 +317,7 @@ The shape of the flow depends on the customer type: - Track terminal `kycStatus` transitions via the `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` webhook (recommended; use the `CUSTOMER.KYB_*` siblings for business customers) or by polling `GET /customers/{customerId}`. On `APPROVED`, unlock funding and money movement. + Track terminal `kycStatus` transitions via the `CUSTOMER.KYB_APPROVED` / `CUSTOMER.KYB_REJECTED` webhook (recommended) or by polling `GET /customers/{customerId}`. `CUSTOMER.KYB_PENDING` also fires when the business is submitted for review — subscribe to it if you want to surface an "under review" state. On `APPROVED`, unlock funding and money movement. From de3d1b8b7244420241d9bc85ec4c0b27cb3a9536 Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Mon, 25 May 2026 04:34:21 -0700 Subject: [PATCH 4/4] add KYB_* cases to webhook handler example (Greptile review) --- mintlify/snippets/kyc/kyc-webhooks.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mintlify/snippets/kyc/kyc-webhooks.mdx b/mintlify/snippets/kyc/kyc-webhooks.mdx index c387e5db..7629fa7f 100644 --- a/mintlify/snippets/kyc/kyc-webhooks.mdx +++ b/mintlify/snippets/kyc/kyc-webhooks.mdx @@ -86,6 +86,23 @@ app.post('/webhooks/kyc-status', async (req, res) => { await markCustomerUnderReview(data.id); break; + case 'CUSTOMER.KYB_APPROVED': + // Business customer decision: activate + await activateCustomer(data.id); + await sendWelcomeEmail(data.id); + break; + + case 'CUSTOMER.KYB_REJECTED': + // Business customer decision: notify + await notifySupport(data.id, 'KYB_REJECTED'); + await sendRejectionEmail(data.id); + break; + + case 'CUSTOMER.KYB_PENDING': + // Intermediate: business customer submitted for review + await markCustomerUnderReview(data.id); + break; + default: // Log unexpected types console.log(`Unexpected webhook type ${type} for customer ${data.id}`);