TL;DR
New customers are non-custodial by default. A fresh customer first enables crypto viaPOST /v2/customers/:id/features { type: crypto_wallet }, then claims non-custodial control of their wallet account through POST /v2/customers/:id/wallets/claim-non-custodial before any wallet address is issued. Calling POST /v2/customers/:id/wallets first returns 422 WALLET_NO_PROVIDER_ACCOUNT pointing back at the claim. Calling claim-non-custodial before the CRYPTO_WALLET feature is approved returns 422 CRYPTO_FEATURE_NOT_APPROVED. For orgs Conduit has configured for custodial wallets, that first POST /v2/customers/:id/wallets instead provisions a custodial account directly — no claim — and payouts skip the cosign gate. The two custody models differ in four observable ways: whether the payout parks at a cosign gate, when chain finality fires, how concurrent payouts are handled, and the Travel Rule pre-broadcast window.
What changes between the two models
Cosign gate
- Custodial: payouts auto-broadcast after compliance checks. No customer-side signature step.
- Non-custodial: payouts park at
pending_cosignand requirePOST /v2/sandbox/payouts/:id/simulate/cosign {outcome: "approved" | "declined"}to drive forward.
The cosign column on this page describes the passkey-required signing mode (the sandbox default). A wallet in a programmatic signing mode parks the same way, but resolution is a machine stamp rather than a passkey: submit a real P-256 stamp via
POST /v2/signing-requests/{id}/approve (real stamps verify in sandbox unmodified), or use the per-signer shortcut POST /v2/sandbox/payouts/:id/simulate-stamp. See Programmatic payout signing and the multi-signer sandbox recipe.Chain finality autopilot
- Custodial: the integrator drives chain confirmation explicitly with
POST /v2/sandbox/payouts/:id/simulate/confirm {outcome: "completed", txHash}. Production: real chain confirmation. - Non-custodial: after cosign approves, the chain-confirm autopilot fires within ~5s and drives the payout to
completed. Nosimulate/confirmcall needed.
Concurrent payouts (non-custodial only)
A non-custodial wallet+chain runs one signing ceremony at a time — and that seat is shared across payouts and conversion/offramp source-leg approvals, so a conversion signing from the same wallet+chain queues behind an in-flight payout (and vice versa). Additional payouts are queued.queuePosition is not in the POST /v2/payouts response — it is populated as the payout enters the queue gate (just after the 202) and is read via GET /v2/payouts/:id. The active/head payout omits the field. The queue is bounded: once full, POST /v2/payouts returns 422 PAYOUT_QUEUE_FULL. Recovery: poll GET /v2/payouts/:id for queuePosition, or wait for the head payout to terminalize. See PAYOUT_QUEUE_FULL reference.
Pre-broadcast Travel Rule reject
A counterparty rejection arrives pre-broadcast as long as the payout has not yet broadcast on-chain. On the non-custodial flow Travel Rule runs before signing, so a counterparty reject is pre-broadcast — and terminates the payout withTRAVEL_RULE_REJECTED — throughout the signing window, right up to broadcast. It only becomes audit-only once the payout has broadcast. Two reliable paths to a pre-broadcast TR-reject:
- Non-custodial wallet, manual counterparty call:
simulate/counterparty-webhook {outcome: "rejected", reason}once the Travel Rule row exists — it is created at the pre-signing Travel Rule step, so wait fortransaction.processing(which fires right after) before calling; a call made earlier, during compliance screening, returns404(no row yet). Fromtransaction.processingthrough broadcast the reject terminates the payout withTRAVEL_RULE_REJECTED, whether the signers have stamped yet or not. - Unconditional pre-broadcast suffix: destination ending
bad7e517(TR_TX_VALIDATE_REJECTED). No manual call needed, and no quorum timing to track.
How to provision a non-custodial wallet
Once the customer is KYB-approved AND has an approvedCRYPTO_WALLET feature on file (submit it via POST /v2/customers/:id/features { type: crypto_wallet } — sandbox-default auto-approve; live-default manual review), call POST /v2/customers/:id/wallets/claim-non-custodial with a roster (admins + signers) and a signingThreshold. The claim endpoint provisions the underlying wallet account, mints the multi-signer roster, and writes the wallets — on every supported chain by default, or just the ones named in an optional chains array. The three EVM chains share one address and one admin approval. After every roster member enrolls, POST /v2/customers/:id/wallets can add a wallet on any chain you excluded. The valid roster shapes depend on the customer’s configured signing mode — passkey-only by default, machine (api_key) signers in the programmatic modes; see Add a crypto wallet. See Sandbox quickstart Step 5 for the full walkthrough.
Lifecycle, side by side
Recipe pairs
- Custodial USDC withdrawal vs non-custodial cosign approved/declined in Withdrawals.
- Fiat withdrawal (no custody flavor) in Withdrawals — Fiat withdrawal.
- Non-custodial pre-broadcast TR-reject in Travel Rule scenarios.
Common pitfalls
- Queue full on non-custodial:
PAYOUT_QUEUE_FULL(422). The wallet+chain signing queue is at capacity — and it counts conversion/offramp source-leg approvals from the same wallet+chain, not just payouts. Wait for the head signing ceremony to terminalize, then retry. SeePAYOUT_QUEUE_FULLreference. - Mixed-case EVM addresses: any mixed-case EVM destination address that is not a valid EIP-55 checksum returns
400 INVALID_ADDRESS_FORMAT. Use all-lowercase. See INVALID_ADDRESS_FORMAT. - Pre/post-broadcast TR-reject timing: the cutoff is the on-chain broadcast, not cosign approval. Non-custodial Travel Rule runs before signing, so a
simulate/counterparty-webhookreject is pre-broadcast (terminatesTRAVEL_RULE_REJECTED) at any point in the signing window — before or aftersimulate/cosignis approved. It becomes audit-only on the TR row only once the payout has broadcast. txHashconstraint onsimulate/confirm: thetxHashyou pass tosimulate/confirmdoes not need to be a real on-chain hash (sandbox is isolated from real chains), but it must be a hex-shaped 32-byte string (0x+ 64 hex chars). Any arbitrary0xdeadbeef...of the right length works.
Error handling on the claim endpoint
POST /v2/customers/:customerId/wallets/claim-non-custodial is the canonical entry point into the multi-signer non-custodial flow. Three 409s plus two 422s are worth handling explicitly:
WALLET_NO_PROVIDER_ACCOUNT(422) — returned fromPOST /v2/customers/:id/walletswhen the customer has no wallet account yet — the common fresh-customer case, before any claim. Claim non-custodial control first withPOST /v2/customers/{id}/wallets/claim-non-custodial, then retry creating the wallet.WALLET_CUSTODY_NOT_CLAIMED(409) — returned fromPOST /v2/customers/:id/walletsonly when the customer already has a custodial wallet account and the custodial path is not enabled for your organization. Fresh non-custodial customers never hit this; where the custodial path is enabled, the same call returns the wallet directly.CUSTOMER_ALREADY_NON_CUSTODIAL(409) — non-custodial control has already been claimed for this customer. The claim endpoint provisions only fresh customers. Use the roster-management endpoints (POST/DELETE /v2/customers/:customerId/wallet-signers,POST .../promote,POST .../demote) to modify signers instead.CUSTOMER_ALREADY_CUSTODIAL(409) — the customer is a legacy custodial customer (provisioned via theCRYPTO_WALLETapplication before the non-custodial gate). Multi-signer roster claim on existing custodial wallets will land in a follow-up; until then these customers stay on the custodial path.CRYPTO_FEATURE_NOT_APPROVED(422) — the customer has no activeCRYPTO_WALLETfeature row, or the feature application is still pending review. SubmitPOST /v2/customers/:id/features { type: crypto_wallet }and wait forstatus: approvedbefore retrying. In sandbox the default is auto-approve; in live the default is manual review unless the org has opted out via the per-orgcryptoEnablementRequiresReviewsetting.
See also
- Sandbox quickstart - end-to-end including the multi-signer
claim-non-custodialflow - Withdrawals - full state diagram and timing
- Travel Rule scenarios
PAYOUT_QUEUE_FULLreferenceINVALID_ADDRESS_FORMATplaybookWALLET_CUSTODY_NOT_CLAIMEDreference