KYC and KYB Flow
A sub-merchant cannot transact until they're verified. Inflow runs the verification under the hood and exposes a single unified contract (kycStatus + nextUrl + nextSteps), so you never have to talk to a verification provider directly. Always treat nextUrl / nextSteps as the source of truth for what to do next — don't hard-code provider URLs on your side.
State machine
Each sub-merchant has a kycStatus. The values you'll see:
kycStatus | Meaning | What you should do |
|---|---|---|
pending | Verification is in progress, or the verification link hasn't been issued yet. | Send the user to nextUrl, or call GET …/kyc/status to re-issue a fresh nextUrl. |
approved | Verification passed. The sub-merchant becomes operable as soon as their wallet finishes provisioning (usually instant). | Start transacting on their behalf. |
rejected | Verification was declined. | Surface this to the seller. Re-onboarding requires a new account (different email). |
approveddoes not always mean operable. Approval and wallet provisioning are atomic — if the wallet provisioning step fails (e.g. wallet provider outage), the status stayspendingwith awalletProvisioningErroruntil the next status poll succeeds. Effectively: a sub-merchant is operable whenkycStatus = "approved"and they have a wallet address. On-behalf-of calls keep returning403 ON_BEHALF_SUBMERCHANT_NOT_OPERABLEin the meantime.
The unified contract
Three responses include the same trio of fields:
POST /api/connect/accounts(creation)GET /api/connect/accounts/:subMerchantId/kyc/status(the source of truth)GET /api/connect/accounts/:subMerchantId(read — same flattening)
Shape:
{
"kycStatus": "pending" | "approved" | "rejected",
"verificationType": "KYC" | "KYB",
"nextUrl": "https://…" | null,
"nextSteps": ["…"]
}nextUrl
nextUrlThe hosted URL the sub-merchant must visit to make progress. null when there is nothing for the user to do (e.g. approved, or waiting for an asynchronous decision).
nextSteps
nextStepsAn ordered string array describing what should happen next. Stable enum values:
| Step | Meaning |
|---|---|
redirect_submerchant_to_url | Redirect the user to nextUrl. |
complete_bridge_tos_and_kyc_flow | KYC flow: user accepts ToS, fills the form, signs. |
wait_for_aiprise_webhook_approval | KYB flow: user filled the form, decision is async. |
check_connect_kyc_status | Re-poll the status endpoint after a delay. |
retry_kyc_bootstrap | The initial verification link failed to be issued; the next status poll will retry. |
For individuals (KYC): ["redirect_submerchant_to_url", "complete_bridge_tos_and_kyc_flow", "check_connect_kyc_status"]
For businesses (KYB): ["redirect_submerchant_to_url", "wait_for_aiprise_webhook_approval", "check_connect_kyc_status"]
After approval: ["check_connect_kyc_status"] — present so polling clients converge cleanly.
Polling for status
GET /api/connect/accounts/:subMerchantId/kyc/status
?kycEndorsement=&redirectUri=
Authorization: Bearer <inflow_apikey>| Query param | When to use |
|---|---|
kycEndorsement ("sepa" | "spei" | "cards") | Rail-aware hint when generating a hosted link. Only relevant if you need a link tuned for a specific payout rail. |
redirectUri | Override the redirect URL used when (re-)generating a hosted link. Useful for routing back to a per-seller page. |
Recommended cadence:
- While the user is on the hosted page: don't poll — wait until they hit your
redirectUrlKyc. - After redirect-back: poll every 2–3 seconds for up to ~30 seconds. KYC is usually instant; KYB can take longer.
- If still pending after 30s: stop polling, surface "we're verifying — we'll email you when it's done", and rely on the next sub-merchant-scoped event in your standard webhook stream (e.g.
payment.received) to wake your backend up.
Retrying a failed bootstrap
If POST /api/connect/accounts returned kycStatus: "pending" with a kycBootstrapError, the sub-merchant exists but doesn't yet have a hosted link. Recovery is just one extra call:
curl "https://api.inflowpay.xyz/api/connect/accounts/$SUB/kyc/status?redirectUri=https://marketplace.example.com/onboarding/$SELLER/done" \
-H "Authorization: Bearer $INFLOW_API_KEY"The status endpoint will detect the missing session and re-issue a fresh nextUrl. No additional state to track on your side.
Handling rejection
kycStatus: "rejected" is terminal for the current sub-merchant account. You cannot re-run verification on the same record — regulatory rules require a fresh customer. The recommended UX:
- Surface a clear message to the seller (Inflow does not return a free-text rejection reason).
- Optionally suspend the sub-merchant via
POST …/suspendso any pending obligations stop processing. - If the seller wants to retry, sign them up with a different email — that creates a brand-new sub-merchant and KYC flow.
Direct-call escape hatches
There are two lower-level routes you'll rarely call directly — they're invoked internally by POST /api/connect/accounts and GET …/kyc/status and exposed in case you need finer control:
POST /api/connect/accounts/:subMerchantId/kyc/start— explicitly start a KYC or KYB session.POST /api/connect/accounts/:subMerchantId/kyc/sync-to-bridge— force-finalise a verified business entity if the asynchronous decision didn't propagate.
In the happy path you don't need either. Reach for them only if Inflow support asks you to.
Security note
KYC state is server-controlled. The verification status, provider IDs, and approval flags are all under Inflow's exclusive control — they cannot be set or overwritten via PATCH /api/connect/accounts/:id, even if you put them inside marketplaceMetadata.
Updated about 20 hours ago