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:

kycStatusMeaningWhat you should do
pendingVerification 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.
approvedVerification passed. The sub-merchant becomes operable as soon as their wallet finishes provisioning (usually instant).Start transacting on their behalf.
rejectedVerification was declined.Surface this to the seller. Re-onboarding requires a new account (different email).

approved does not always mean operable. Approval and wallet provisioning are atomic — if the wallet provisioning step fails (e.g. wallet provider outage), the status stays pending with a walletProvisioningError until the next status poll succeeds. Effectively: a sub-merchant is operable when kycStatus = "approved" and they have a wallet address. On-behalf-of calls keep returning 403 ON_BEHALF_SUBMERCHANT_NOT_OPERABLE in 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

The 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

An ordered string array describing what should happen next. Stable enum values:

StepMeaning
redirect_submerchant_to_urlRedirect the user to nextUrl.
complete_bridge_tos_and_kyc_flowKYC flow: user accepts ToS, fills the form, signs.
wait_for_aiprise_webhook_approvalKYB flow: user filled the form, decision is async.
check_connect_kyc_statusRe-poll the status endpoint after a delay.
retry_kyc_bootstrapThe 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 paramWhen 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.
redirectUriOverride 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:

  1. Surface a clear message to the seller (Inflow does not return a free-text rejection reason).
  2. Optionally suspend the sub-merchant via POST …/suspend so any pending obligations stop processing.
  3. 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.