Skip to main content
Onboard a business customer along the golden path: discover what a country requires, upload the documents, submit the application, and react to the result. Conduit is submit-and-listen. Submit one application. Receive a webhook when it is approved or rejected. There is nothing to poll. For the end-to-end state and recovery map, see The Onboarding Lifecycle.

Prerequisites

  • An API key for the environment you’re integrating against. See Authentication.
  • A webhook endpoint subscribed to application.approved and application.rejected. See Webhooks.
  • The customer’s primary country (ISO 3166-1 alpha-2 or alpha-3, e.g. US or USA).

Flow

  1. Discover the onboarding requirements for the customer’s country.
  2. Upload each required document and keep the returned doc_... ids.
  3. Submit the application with the collected fields and document ids.
  4. Listen for application.approved or application.rejected.
  5. Fetch the new customer.

Step 1 — Discover requirements

Requirements are country-specific. Call the discovery endpoint to learn which fields and documents to collect. Never hardcode them.
curl 'https://api.conduit.financial/v2/onboarding/requirements?country=US' \
  -H "x-api-key: YOUR_API_KEY"
The response has three parts: fields[] (the data to collect), documents[] (the document checklist), and individualRequirements[] (per-person rules). A control person is a beneficial owner or controlling person of the business — the individuals you list in ownership.persons[]; individualRequirements[] tells you how many each country requires and which role each must hold.
{
  "context": "onboarding",
  "country": "USA",
  "fields": [
    {
      "name": "businessInfo.taxId",
      "label": "Tax ID (EIN)",
      "type": "string",
      "required": true,
      "constraints": { "pattern": "^\\d{2}-\\d{7}$", "example": "12-3456789" }
    },
    {
      "name": "companyClassification.legalStructure",
      "label": "Legal structure",
      "type": "enum",
      "required": true,
      "allowedValues": ["LLC", "C_CORP", "S_CORP", "PARTNERSHIP"]
    }
  ],
  "documents": [
    {
      "canonicalType": "articles_of_incorporation",
      "title": "Articles of Incorporation",
      "minCount": 1
    }
  ],
  "individualRequirements": [
    {
      "role": "BENEFICIAL_OWNER",
      "minCount": 1,
      "ownershipThreshold": 25,
      "documents": [
        { "canonicalType": "government_id", "title": "Government-issued ID" }
      ]
    }
  ]
}
Each fields[].name is a dot-path. Render your collection UI from fields[], and validate against each field’s type, required, and constraints. See Requirements by Country for the full schema.
Country codes are normalized server-side. You can send alpha-2 (US) or alpha-3 (USA); the response always echoes alpha-3.

Step 2 — Upload documents

Upload each document the requirements ask for. The endpoint is multipart with an optional purpose form field. Ordinary onboarding documents can omit it; identity attestations use purpose=kyc. Repeat once per file.
curl https://api.conduit.financial/v2/documents \
  -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -F "file=@articles-of-incorporation.pdf"
{ "id": "doc_2xKjF9mQb7vN4hL1pR3w8t" }
Keep each returned id. Customer-level documents (e.g. incorporation papers) go in the top-level documentIds[]; documents that belong to a specific person (e.g. their government ID) go in that person’s ownership.persons[].documentIds[].

Step 3 — Submit the application

Turn each fields[].name dot-path into a nested object (businessInfo.taxIdbusinessInfo: { taxId }), attach the document ids, and POST to /v2/onboarding.
curl https://api.conduit.financial/v2/onboarding \
  -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: 8f3a1c2e-1b4d-4e9a-9c7f-2a6b5d8e1f00" \
  -d '{
    "clientReferenceId": "your-ref-001",
    "businessInfo": { "businessName": "Acme Inc", "taxId": "12-3456789" },
    "registeredAddress": { "country": "US", "addressLine1": "1 Main St", "city": "New York", "state": "NY", "zipcode": "10001" },
    "companyClassification": { "legalStructure": "C_CORP", "incorporationDate": "2020-01-15" },
    "ownership": {
      "persons": [
        {
          "roles": ["BENEFICIAL_OWNER", "CONTROLLING_PERSON"],
          "firstName": "Jane",
          "lastName": "Doe",
          "ownershipPercent": "100",
          "documentIds": ["doc_2aGov7IdQb7vN4hL1pR3w8"]
        }
      ]
    },
    "documentIds": ["doc_2xKjF9mQb7vN4hL1pR3w8t"]
  }'
The endpoint returns 202 Accepted with the new application in processing. The customer does not exist yet. customerId is null until the application is approved.
{
  "id": "app_2xKjF9mQb7vN4hL1pR3w8t",
  "customerId": null,
  "clientReferenceId": "your-ref-001",
  "type": "CUSTOMER_ONBOARDING",
  "status": "processing",
  "asset": null,
  "submittedAt": "2026-01-15T09:30:00.000Z",
  "reviewedAt": null,
  "createdAt": "2026-01-15T09:30:00.000Z",
  "updatedAt": "2026-01-15T09:30:00.000Z"
}
Pass your own clientReferenceId to correlate the application with a record in your system. It is echoed back on the response and on every webhook for this application.
Idempotency-Key is required. Reuse the same key when retrying a submission so a network failure can’t create a duplicate application. A submission whose tax ID or beneficial-owner government ID matches an existing active application is rejected with 409 ONBOARDING_ALREADY_SUBMITTED.

Step 4 — Listen for the result

When review completes, Conduit delivers one of two webhooks. Both include your clientReferenceId. application.approved — the customer is now active. customerId is populated.
{
  "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
  "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
  "clientReferenceId": "your-ref-001"
}
application.rejected — no customer is created. reason explains why.
{
  "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
  "customerId": null,
  "reason": "Application does not meet compliance requirements",
  "clientReferenceId": "your-ref-001"
}

Step 5 — Fetch the customer

On approval, retrieve the customer with the customerId from the webhook.
curl https://api.conduit.financial/v2/customers/{customerId} \
  -H "x-api-key: YOUR_API_KEY"

Handling a rejection

A rejected application is terminal. No customer is created, and customerId stays null. The application.rejected webhook carries a human-readable reason when one is available. There is no per-field breakdown. The reason arrives on the webhook only, so persist it when you receive it. Confirm an application’s status at any time. This helps if a webhook was missed:
curl https://api.conduit.financial/v2/applications/{applicationId} \
  -H "x-api-key: YOUR_API_KEY"
{
  "id": "app_2xKjF9mQb7vN4hL1pR3w8t",
  "customerId": null,
  "clientReferenceId": "your-ref-001",
  "type": "CUSTOMER_ONBOARDING",
  "status": "rejected",
  "asset": null,
  "submittedAt": "2026-01-15T09:30:00.000Z",
  "reviewedAt": "2026-01-15T09:42:00.000Z",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "updatedAt": "2026-01-15T09:42:00.000Z"
}
The response reports status but not the rejection reason. Capture the reason from the webhook.

Correct and resubmit

A rejection does not block the business. A rejected application no longer counts as active. Once you’ve corrected the data, submit a fresh application for the same tax ID and beneficial owners. Submit it as a new POST /v2/onboarding with:
  • a new Idempotency-Key — reusing the rejected submission’s key replays its original response instead of creating a new application.
  • the same clientReferenceId — keeps every attempt correlated to one record in your system.
To abandon an application that is still in review, before any decision, call POST /v2/applications/{applicationId}/cancel. Approved and rejected applications are terminal and cannot be cancelled.

What happens next

The customer is active but has no features yet. Add a Virtual Account so they can receive funds — see Add Virtual Accounts.