> ## Documentation Index
> Fetch the complete documentation index at: https://v2.docs.conduit.financial/llms.txt
> Use this file to discover all available pages before exploring further.

# The Onboarding Lifecycle

> Follow a business customer from submitted application through verification to active — across the full-KYC and KYC-reliance paths

This is the map. The [Onboard a Customer](/guides/onboard-customer) and [Onboard with KYC Reliance](/guides/onboard-with-kyc-reliance) guides each walk one path step by step. This guide connects them — what state your customer is in at each point, what moves them forward, which webhook tells you it happened, and what to do when something goes wrong.

Onboarding is **submit-and-listen**. You submit one application and wait for a webhook. Nothing to poll, and — unless you deliver verification links yourself — no intermediate states to act on between submission and the final decision. (Self-delivery is the one exception: you forward each person's link as its `idv_link.created` event arrives.)

<Note>
  **Test this flow in sandbox.** Drive it end-to-end with simulated money and deterministic controls — start with the [sandbox quickstart](/sandbox/quickstart), then [customer KYC simulation](/sandbox/customer-kyc) for this flow, and the [cheat sheet](/sandbox/cheat-sheet) for every magic value and simulate endpoint.
</Note>

## The two paths, one shape

Every business customer travels the same journey: you submit an application, Conduit verifies the business and the people behind it, and the customer either becomes active or the application is rejected. Only one thing differs: **how each control person proves their identity**. A control person is a beneficial owner or controlling person of the business — the individuals you list in `ownership.persons[]`; the discovery response's `individualRequirements[]` tells you how many each country requires.

|                                   | Full KYC                                                     | KYC reliance                                                   |
| --------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------- |
| Identity verification             | Each control person completes a Conduit-hosted identity flow | You upload one verification report per control person          |
| Where the identity proof attaches | Nothing — captured in the hosted flow                        | `ownership.persons[i].documentIds[]`                           |
| Availability                      | Default                                                      | Must be enabled for your organization first                    |
| Detailed guide                    | [Onboard a Customer](/guides/onboard-customer)               | [Onboard with KYC Reliance](/guides/onboard-with-kyc-reliance) |

Everything else — business-entity fields, KYB documents, each person's address and proof-of-address documents, screening, the review pipeline, the states, and the decision webhooks — is identical. (Person-verification events like `idv_link.created` fire only on the standard path — reliance runs no hosted checks, so there are no links to deliver.) The rest follows the shared journey and calls out the reliance branch only where it diverges.

<Note>
  KYC reliance is not on by default. If it is not enabled for your
  organization, attaching verification reports will not satisfy the identity
  requirement — each person will still be expected to complete a hosted flow.
  Contact your Conduit account manager to enable it.
</Note>

## The journey

<Steps>
  <Step title="Before you start — discover and collect">
    There is no customer and no application yet.

    Requirements are country-specific, so call `GET /v2/onboarding/requirements?country=...` to learn which fields and documents to collect, then upload each document with `POST /v2/documents` and keep the returned `doc_...` ids. Never hardcode the requirements — they vary by country and change over time.

    Discovery also carries **per-person requirements** that apply on both paths: each control person's role in `individualRequirements[]` lists the fields they must submit (`fields[]`) and the documents they must supply (`documents[]`). Which rows appear is jurisdiction- and diligence-dependent — a residential `address` and a proof of address are listed where enhanced due diligence applies. Collect whatever discovery lists for each person and hold each `doc_...` id to attach to that person.

    **Reliance branch:** additionally, upload one identity verification report (a PDF exported from your own KYC provider) per control person, and hold each `doc_...` id to attach to that person.

    See [Onboard a Customer → Discover requirements](/guides/onboard-customer) for the full field and document walkthrough, and [requirements reference](/api-reference/customer-onboarding/discover-onboarding-requirements-for-a-country) for the response schema.
  </Step>

  <Step title="Submit the application">
    Submit everything you collected to `POST /v2/onboarding` with a required `Idempotency-Key`. The endpoint returns `202 Accepted`.

    **State: `processing`.** The application now exists and is in review. No customer exists yet — `customerId` is absent from the response until approval. Nothing else to do here. The next thing that happens is a webhook.

    Per-person data attaches to the person, on both paths: a person's residential `address` (when required) goes under `ownership.persons[i].address`, and their proof-of-address (and any other per-person document) id goes in `ownership.persons[i].documentIds[]`. The top-level `documentIds[]` carries business-entity documents only.

    **Reliance branch:** each person's identity verification report id goes in that same `ownership.persons[i].documentIds[]`, alongside any other per-person documents.

    <Warning>
      Reuse the same `Idempotency-Key` if a submission times out, so a retry
      can't create a duplicate application. A submission whose tax ID or a
      control person's identity matches an existing **active** application is
      rejected with `409 ONBOARDING_ALREADY_SUBMITTED`.
    </Warning>
  </Step>

  <Step title="Verification runs (you wait)">
    **State: still `processing`.** Conduit verifies the business and screens the people behind it. This is where the two paths physically differ:

    * **Full KYC** — each declared control person completes a Conduit-hosted identity check (a government-ID capture, plus a live selfie where enhanced due diligence applies). Conduit emails each person their link by default and, independently, fires an `idv_link.created` webhook per verification so you can deliver the link yourself (also pullable from `POST /v2/applications/{applicationId}/persons/{personReferenceId}/idv-link`). See [Onboard a Customer](/guides/onboard-customer) for the delivery details.
    * **KYC reliance** — Conduit reads the report you uploaded for each control person and matches it against the data you submitted, centering on name and date of birth. A clean match clears the identity requirement automatically. A mismatch, an unreadable report, or an unrecognized provider routes the application to a manual reviewer.

    The application stays in `processing` from your side. If you rely on Conduit's direct email (the default), there is nothing to do but wait. If you deliver links yourself, this is the one step that needs your action mid-flight: hand each link to its person as the `idv_link.created` webhook arrives (or pull it from the idv-link endpoint if an event went missing), then wait. Verification can take seconds to days depending on what review finds. Wait for the decision webhook in the next step.
  </Step>

  <Step title="The decision arrives">
    Review completes and the application reaches a terminal state. Exactly one of two outcomes fires, and every payload echoes your `clientReferenceId`.

    **Approved — state: `approved`.** The customer now exists. Two webhooks fire as a pair on first approval:

    * `application.approved` — carries the populated `customerId`.
    * `customer.created` — the new customer, same `applicationId` and `customerId`.

    Because they are a pair, dedupe on `(applicationId, customerId)` if your handler reacts to either. An idempotent re-approval of an already-approved application does **not** re-emit them.

    **Rejected — state: `rejected`.** No customer is created; `customerId` is absent from the webhook.

    * `application.rejected` — carries `failureCode` (machine-readable) and `failureMessage` (human-readable) when a specific reason is available. Both fields are also readable from `GET /v2/applications/{applicationId}`.

    Both `approved` and `rejected` are terminal. See [the rejection branch](#the-rejection-branch) below for how to recover.
  </Step>

  <Step title="The customer is active">
    **State: `approved`, customer exists.** Fetch it with `GET /v2/customers/{customerId}` using the id from the webhook.

    The customer is active but has no features yet — it can't receive or move money until you add one. The usual next step is to give them a place to receive funds. See [Add Virtual Accounts](/guides/add-virtual-accounts).

    <Warning>
      Operations that require a fully-onboarded customer (adding features,
      creating wallets, transacting) fail with `422 CUSTOMER_NOT_ONBOARDED`
      until the application is approved. Gate those calls on
      `application.approved` rather than attempting them and handling the error.
      See [CUSTOMER\_NOT\_ONBOARDED](/errors/customer-not-onboarded).
    </Warning>
  </Step>
</Steps>

## Checking state without a webhook

Webhooks are the intended signal, but if you miss one you can read the current state at any time. The response carries `status`, and — on a rejected application — `failureCode` and `failureMessage`.

```bash theme={null}
curl https://api.conduit.financial/v2/applications/{applicationId} \
  -H "x-api-key: YOUR_API_KEY"
```

```json theme={null}
{
  "id": "app_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "customer_onboarding",
  "status": "rejected",
  "failureCode": "compliance_denied",
  "failureMessage": "Application does not meet compliance requirements",
  "submittedAt": "2026-01-15T09:30:00.000Z",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "updatedAt": "2026-01-15T09:42:00.000Z"
}
```

You can also list every application for a customer with `GET /v2/applications`, which carries the same `failureCode`/`failureMessage` pair on each rejected row. The full set of `status` values and their meanings lives in the [application reference](/api-reference/applications/get-an-application-by-id) — treat that as authoritative rather than memorizing them here.

## The rejection branch

A rejection does not block the business. A `rejected` application is terminal and creates no customer, but it no longer counts as active. Once you've corrected the data, submit a **fresh** `POST /v2/onboarding`:

* use a **new `Idempotency-Key`** — reusing the rejected submission's key replays its original response instead of creating a new application.
* keep the **same `clientReferenceId`** — so every attempt stays correlated to one record in your system.

<Note>
  To abandon an application that is still in review **before** any decision,
  call `POST /v2/applications/{applicationId}/cancel`. Approved, rejected, and
  already-cancelled applications are terminal and cannot be cancelled — submit
  a new application instead.
</Note>

## When something goes wrong

| Symptom                                                     | Where to look                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Submission rejected with `409 ONBOARDING_ALREADY_SUBMITTED` | An active application already exists for that tax ID or control person. Cancel or await the existing one.                                                                                                                                                                                                                                                                                                                                    |
| A reliance report didn't clear the identity requirement     | [Onboard with KYC Reliance](/guides/onboard-with-kyc-reliance) — acceptance and rejection expectations                                                                                                                                                                                                                                                                                                                                       |
| A required document is missing or has the wrong purpose     | [DOCUMENTATION\_REQUIRED](/errors/documentation-required)                                                                                                                                                                                                                                                                                                                                                                                    |
| A person never received their verification link             | Check the direct-email setting, or subscribe to `idv_link.created` and deliver it yourself; while the person's verification session is pending, their link is available via `POST /v2/applications/{applicationId}/persons/{personReferenceId}/idv-link` (404 before the session exists, 409 once it settles — see the endpoint's status handling). See [Onboard a Customer → Each person verifies their identity](/guides/onboard-customer) |
| An operation failed because the customer isn't active yet   | [CUSTOMER\_NOT\_ONBOARDED](/errors/customer-not-onboarded)                                                                                                                                                                                                                                                                                                                                                                                   |

## Where to go next

* [Onboard a Customer](/guides/onboard-customer) — the full-KYC path, end to end
* [Onboard with KYC Reliance](/guides/onboard-with-kyc-reliance) — the reliance path, end to end
* [Add Virtual Accounts](/guides/add-virtual-accounts) — the usual first step with a newly active customer
* [Webhooks](/webhooks) — subscribe to `application.approved`, `application.rejected`, `customer.created`, and `idv_link.created` (each person's verification link, to deliver yourself)
