Skip to main content
This page onboards an individual: a natural person who opens an account in their own name. It goes from requirements discovery to a completed payout, then funds the account again from crypto. The business path is the Sandbox quickstart. For what an individual owes, see Onboarding an Individual.

Prerequisites

  • A sandbox API key (ck_sandbox_...) and a registered webhook endpoint. Set them up as in the Sandbox quickstart → Prerequisites.
  • Conduit enables individual onboarding for your organization after a review, separately for sandbox and live. Contact your Conduit account manager to ask for it. Until it is enabled, Steps 1 and 3 return 422 SUBJECT_REQUIREMENTS_NOT_AVAILABLE.
  • curl, jq and uuidgen on your PATH.

Step 1 - Discover what the person owes

Send subjectType=individual. For an individual, country is the person’s country of residence.
The response (abbreviated: each allowedValues list and most fields are cut):
Top-level fields[] are the account holder’s activity, politically-exposed-person and consent answers. The one ACCOUNT_HOLDER row carries the person’s own fields and documents. A field with conditions is required only while its conditions hold: here the tax ID follows the person’s residential country, address.country. This example asks for the tax ID of a person who lives in the US, whatever their nationality. A US national sends SSN wherever they live, and a non-US national who lives in the US sends SSN or ITIN. Everyone else sends a NATIONAL_ID, PASSPORT, DRIVER_LICENSE or RESIDENT_CARD, with taxIdCountry set to the country that issued it. Add nationality to the requirements call to get the list for a given nationality. Submit refuses ITIN from a US national, a missing tax ID from a US resident, a malformed SSN or ITIN, and a restricted national number, with 422 ONBOARDING_NOT_READY. See Tax identifier for the restricted numbers. identityDocumentExpiryDate is the expiry date of the identity document your provider verified, and submit refuses a date before today. identityVerificationDate is the date your provider completed the verification, as the report shows it. It is optional, but send it. Without it, approval depends on reading the date from the report, and the application can wait for a manual review.

Step 2 - Upload the identity verification report and ID

You verify the person with your own identity-verification provider. Upload the provider’s report and the original government ID it verified, each with purpose=kyc. Upload the back of the ID as one more file if the document has one. See Person-level evidence for what each must contain.
Each 201 Created returns { "id": "doc_...", "purpose": "kyc", "classificationStatus": "pending", ... }. Sandbox reviews the report as live does. The review classifies it, reads the person’s details from it, and compares them with the person you submit. The report from Prerequisites names the person of Step 3. The report must print a labelled verification date, for example Verification date: 2026-10-01. Its person data must not carry test markers such as ZZZTEST, a test name or an example.org address. A report that names someone else, that looks like a placeholder, or that is not a report keeps the application in processing until you decide it in Step 4. So does a missing or unreadable ID document. Sandbox does not check if the ID document is authentic, so the sample file from Prerequisites counts as the person’s ID. The file must still read as a passport, national ID card or driver’s licence. Live checks that the ID is a real document. The same applies when the verification is more than a year old, when the report’s date differs from the identityVerificationDate you send by more than a few days, or when you do not send identityVerificationDate and the report has no readable date.

Step 3 - Submit the application

One person, the account holder, with the report id and the ID document id in their documentIds[]. No business envelope.
202 Accepted returns the application in processing. The phone above is in national format, which the API accepts because the person’s address.country completes it (phone numbers). A second submission for the same tax ID is refused with 409, also when it differs only in dashes or spaces: see Submit an individual application.

Step 4 - Wait for the approval and read the customer

The person’s sanctions screen clears and the report matches, so the application is approved with no call from you, usually within a few minutes. Your webhook endpoint receives application.approved, carrying customerId, paired with customer.created. Take customerId from that delivery and read the customer:
If application.approved has not arrived after a few minutes, the review is waiting for a decision, usually because the report did not match the person (Step 2 lists the causes). Approve it with the sandbox decision lever; application.approved then fires:
Branch on type: "individual". Countries read back as ISO 3166-1 alpha-2, except activity.countriesOfActivity, which keeps the alpha-3 codes you sent. The response does not carry the tax ID number.

Step 5 - Open a USD account

Feature discovery works as for a business. Its country is the person’s country of residence:
The feature application returns 202 with status: "approved". Provisioning runs asynchronously; your webhook endpoint receives virtual_account.activated, carrying virtualAccountId, within a few seconds. Take it from that delivery, or read it once the account shows active:
Its deposit instructions name the person as beneficiary, at their home address:

Step 6 - Fund the account

202 Accepted returns { "externalReference": "sandbox_..." }. Your webhook endpoint receives transaction.created followed by transaction.completed for the deposit, usually within seconds, though the first one after a quiet period can take up to a minute. Wait for transaction.completed before you pay out; the account’s available balance then reads 1000.00:
Failure paths are in Deposits.

Step 7 - Pay out

Upload a supporting document, send the payout, then approve the document review. The payout declares purpose: purchase_of_goods, one of the purposes an individual can declare; Transact lists them all. To pay the person’s own account at another bank, declare personal_transfer_of_funds against a whitelisted recipient instead, and no document is needed. On a US rail the recipient’s bankAddress is optional: Conduit derives it from routingNumber.
The document review opens a few seconds after the payout is created; your webhook endpoint receives transaction.under_review when it is open. Call simulate-review-approve after that delivery, not before: a call sent too early returns 404 PAYOUT_NOT_FOUND.
After the 200, the payout resumes, and your webhook endpoint receives transaction.completed, transaction.failed or transaction.cancelled for its terminal status. Read it once that delivery arrives:
A completed GET /v2/payouts/${TXN_ID} reads status: "completed". source.assetAmount is the amount debited from the account. destination.assetAmount is the amount the recipient receives. fees[] lists the fees that your organization’s pricing sets, and it is empty when none apply. The account’s available balance falls by source.assetAmount. Fee rules and the rest of the payout lifecycle are in Send a payout.

Step 8 - Fund the account from crypto

An offramp turns crypto into dollars. Create an order with no source. Conduit returns a deposit address. Send testnet USDC to that address. When the deposit clears, the order runs by itself and credits the account from Step 5. This is a deposit-funded order. The rules are in Deposit-funded orders, and the sandbox variants are in Offramps. The person in this guide lives in the United States. On live, Conduit refuses their offramps, onramps and conversions. Sandbox runs them. See Customers in the United States. The sandbox runs on a real testnet. You need a wallet you control on Ethereum Sepolia. Before you create the order, fund that wallet: request testnet USDC for it from the Circle faucet (Ethereum Sepolia), and get Sepolia ETH for gas from any Sepolia faucet. The Circle faucet issues USDC only, so the ETH comes from elsewhere. To offramp USDT, use the same calls with "code": "USDT". The sandbox accepts only one Sepolia USDT token, “Test Tether USD” at 0x7169d38820dfd117c3fa1f22a697dba58d90ba06. A token from any other Sepolia contract named USDT does not fund the order. The Circle faucet does not issue this token, and Conduit does not run a faucet for it, so you can get it only from a wallet that already holds it. If you have none, do this step with USDC. First, register that wallet as a sending address. Conduit accepts funds into a deposit address only from a registered address.
201 Created returns the address in registered status. A send from an address you did not register is returned to the sender and does not fund the order. Then create the order. Send sourceAsset and no source. Send no autoExecute.
202 Accepted returns the order in pending, with no source. depositInstructions[0] holds the deposit address and expiresAt. Fund the order before expiresAt. If nothing arrives by then, Conduit cancels the order and order.cancelled fires with reason: "expired". Send now. Send the totalDebit amount of testnet USDC from SENDER_ADDRESS to DEPOSIT_ADDRESS on Ethereum Sepolia, before depositInstructions[0].expiresAt. Your webhook endpoint receives order.succeeded (or order.failed / order.cancelled) once the deposit confirms. The wait is mostly chain finality, so expect a few minutes after the send. Read the order once that delivery arrives:
The order goes from pending to succeeded. The credited amount is destinationAsset.amount on the order (GET /v2/orders/{id}), and it follows your organization’s pricing. The funding transfer is also a deposit transaction, readable through GET /v2/transactions. The account’s available balance rises by the credited amount.

Step 9 - Test the compliance paths

Each of these paths starts with one call. After it, the same transitions and events occur as on live. A sanctions match. Run Steps 2 and 3 again for a second person with the first name Ivan and the last name Sanctionov. Give that person a report in their own name, and a new clientReferenceId, email and tax ID. The screen finds a sanctions match, so the application stays processing and is not approved by itself. On live it waits for Conduit compliance. In sandbox, decide it with the decision lever, for example { "outcome": "rejected", "category": "compliance" }. A restriction. Restrict the customer, then lift the restriction:
full_freeze and withdrawals_suspended fire customer.restricted. conversions_disabled fires no event. While the restriction is active, a money movement that it blocks returns 403 CAPABILITY_SUSPENDED. A full_freeze also refuses a new virtual account, wallet or payout destination for the customer with the same error. The lift fires no event. A request for information. Open an RFI on the customer:
The RFI is open and rfi.published fires. Answer it with POST /v2/rfis/{id}/responses, then close it with POST /v2/sandbox/rfis/{id}/simulate/resolve or ask again with POST /v2/sandbox/rfis/{id}/simulate/more-info.

See also