B2B invoicing and settlement
Onboard businesses through KYB, give each one a receiving account, and settle invoices on chain.
Your customers are businesses. Their buyers wire the virtual account, and USDC lands in the seller's treasury address.
The shape
Build order
| Step | Call | Notes |
|---|---|---|
| 1. Onboard the business | POST /api/customers with kind: "business" | legal_name is optional: we read it from the registration document. Send business_kind so missing lists every document its legal form owes from the start. Requesting endorsements: ["endo_usd"] needs email. Send tax_id, or a proof_of_tax_identification: the review needs it |
| 2. Add the people behind it | controllers on the same call | One array with role flags, not three parallel lists. Each controller carries its own identity document and proof of address |
| 3. Submit for KYB | POST /api/customers/{customer_id}/verifications | Businesses owe the documents their legal form needs (see Documents we ask for) and each controller's documents. We read the company and controller details from them, and the missing bucket lists only what they could not supply |
| 4. Create the account | POST /api/customers/{customer_id}/virtual_accounts | One account number receives every rail in payment_rails, wire included |
| 5. Put the details on the invoice | deposit_instructions from step 4 | account_number, the routing number for the rail you ask buyers to use, account_holder_name, account_holder_address |
| 6. Reconcile | GET /api/transactions, tx_hash on each payout | See Reconciliation |
Controllers, not three lists
The same person is often a beneficial owner, a director, and the
authorized signer, so controllers is one array with role flags rather
than a list per role:
"controllers": [
{
"first_name": "Max",
"last_name": "Muster",
"date_of_birth": "1981-07-02",
"has_ownership": true,
"ownership_percentage": 60,
"has_control": true,
"is_signer": true,
"role": "ceo",
"id_document": {"kind": "passport", "number": "C9876"}
},
{"first_name": "Erika", "last_name": "Muster", "is_director": true}
]
ownership_percentage is required whenever has_ownership is set: an
owner without a declared stake cannot be risk assessed.
Each controller comes back with an id. Send it with that person on a later
update of controllers to keep them and the documents held for them. See
People on a business.
Wire and ACH on one account
Omit payment_rails at creation and the account receives over every USD
rail the bank serves today, all on the same account number. The buyer
picks the rail when paying. Wire carries a far higher per-transaction
ceiling than ACH, which suits invoice settlement. For the per-rail limits,
see Coverage or GET /api/corridors.
deposit_instructions carries routing_number_ach and
routing_number_wire where the bank differentiates them. Put the routing
number for the rail you ask buyers to use on the invoice.
You cannot create a second account in the same currency for the same
customer: that returns 400 virtual_account_exists, because it is the same
underlying account. PATCH the existing one's destination instead.
Matching payments to invoices
remittance_info carries what the sender typed, and sender_name carries
who they are. Neither is guaranteed to be clean, so do not build your only
matching path on them.
The reliable identifier is the account itself: one virtual account per
customer means every arrival on it belongs to that customer.
network_reference on the deposit is the bank network's own end-to-end id,
which a bank can trace when a buyer says the money left days ago.
What to watch
deposit.on_holdneeds a human. Page someone for a held payment on a large invoice.deposit.returnedmeans money moved and came back. Reconcile it as two movements, and never auto-resend.- KYB takes longer than KYC. Do not build a flow where the buyer is waiting on the seller's verification: onboard the seller before you let them issue an invoice.