Environment
API Key *
Sandbox Key:
WL-SANDBOX-TEST-1234-5678-9012Demo Customer ID:
cmh8x9k5w01nla23ccabmclkq (KYC Approved)
API Base URLs
Create Customer with KYC
Create a new customer and automatically generate a KYC verification link. All customers must complete KYC before creating virtual accounts.
Request Body
POST /api/trustodi/v1/{clientId}/customersPOST /api/trustodi/v1/{clientId}/customers
Both paths resolve to the same handler. Use the X-API-Key header.
type (INDIVIDUAL | BUSINESS), email
phone,
address.streetLine1, address.city, address.subdivision, address.postalCode, address.countryCode,
accountPurpose, sourceOfFunds, expectedMonthlyPaymentsUSD,
governmentIssuedIdentification.{type, countryCode, number, frontImage}
— plus, for INDIVIDUAL: firstName, lastName, dateOfBirth, employmentStatus, mostRecentOccupation
— plus, for BUSINESS: businessName, registrationNumber, registrationDate, businessType, industry, website, description, taxIdentificationNumber.
referenceId, middleName, transliteratedFirstName/MiddleName/LastName, transliteratedAddress,
phoneCountryCode, ipAddress, capabilities, redirectUrl,
accountPurposeExplanation (required when accountPurpose=OTHER),
actingAsIntermediary, isDao (BUSINESS),
governmentIssuedIdentification.{backImage, issuanceDate, expirationDate},
supportingDocuments[].
type: INDIVIDUAL, BUSINESS ·
governmentIssuedIdentification.type: PASSPORT, NATIONAL_ID, DRIVERS_LICENSE, RESIDENCE_PERMIT ·
employmentStatus: EMPLOYED, HOMEMAKER, RETIRED, SELF_EMPLOYED, STUDENT, UNEMPLOYED ·
expectedMonthlyPaymentsUSD: UNDER_FIVE_THOUSAND, FIVE_THOUSAND_TO_TEN_THOUSAND, TEN_THOUSAND_TO_FIFTY_THOUSAND, FIFTY_THOUSAND_PLUS ·
sourceOfFunds: COMPANY_FUNDS, ECOMMERCE_RESELLER, GAMBLING_PROCEEDS, GIFTS, GOVERNMENT_BENEFITS, INHERITANCE, INVESTMENTS_LOANS, PENSION_RETIREMENT, SALARY, SALE_OF_ASSETS_REAL_ESTATE, SAVINGS, SOMEONE_ELSES_FUNDS ·
accountPurpose: 11 values (CHARITABLE_DONATIONS, ECOMMERCE_RETAIL_PAYMENTS, INVESTMENT_PURPOSES, OPERATING_A_COMPANY, OTHER, PAYMENTS_TO_FRIENDS_OR_FAMILY_ABROAD, PERSONAL_OR_LIVING_EXPENSES, PROTECT_WEALTH, PURCHASE_GOODS_AND_SERVICES, RECEIVE_PAYMENT_FOR_FREELANCING, RECEIVE_SALARY) ·
businessType: see contract module.
first_name / firstName) and normalises legacy enum values (LLC → LIMITED_LIABILITY_COMPANY). Errors are returned as { code: "TRUSTODI_*", message, field, details: [...] }. All dates are YYYY-MM-DD; phone is E.164; country codes are ISO 3166-1 alpha-2; document images are base64 data URIs.
redirectUrl / redirect_url field controls where your customer lands after finishing (or exiting) verification. Resolution precedence: per-request redirect_url → your partner-level default (set in Partner Dashboard → API Management → KYC/KYB Completion Redirect) → the TRUSTODI completion page. Set the dashboard default once so customers return to your own site without sending redirect_url on every call.
Get Customer
Retrieve detailed information about a specific customer by their ID.
Path Parameters
Update Customer
Update an existing customer's information.
Path Parameters
Request Body
PUT /api/trustodi/v1/{clientId}/customers/{customerId}PUT /api/trustodi/v1/{clientId}/customers/{customerId}
Both paths resolve to the same handler.
type or email. Any subset of the contract fields may be submitted. Field aliases (camelCase ↔ snake_case, legacy enum values) are accepted on input. Errors are returned as { code: "TRUSTODI_*", message, field, details: [...] }.
Delete Customer
Permanently delete a customer and their associated data. This action cannot be undone.
Path Parameters
Generate KYC Link
Generate a KYC verification link for a customer. The link can be sent to the customer to complete their identity verification.
Request Body
redirect_url on this call →
your partner-level default (set in Partner Dashboard → API Management → KYC/KYB Completion Redirect) →
the TRUSTODI completion page. Set your dashboard default once so customers return to your own site without
sending redirect_url on every request.
Create Customer (Document Sharing)
Create a customer by submitting KYC documents directly. This bypasses the KYC link flow and allows partners to submit customer data and documents programmatically.
redirect_url on this call →
your partner-level default (set in Partner Dashboard → API Management → KYC/KYB Completion
Redirect) → the TRUSTODI completion page. Set your default once in the dashboard so every
customer lands back on your own site without sending redirect_url on each request.
Personal Information
["USD","OTHER"] unlocks US rails but requires enhanced compliance checks and longer approval. ["OTHER"] for non-US only.Address
Government Issued ID
["USD","OTHER"] unlocks US rails but requires enhanced compliance checks and longer approval. ["OTHER"] for non-US only.Required
BUSINESS_FORMATIONBANK_STATEMENTOWNERSHIP_INFORMATIONOptional
BUSINESS_LICENSEOPERATING_LICENSEPROOF_OF_ADDRESSTAX_DOCUMENTMEMORANDUM_OF_ASSOCIATIONSHAREHOLDER_REGISTRYCERTIFICATE_OF_GOOD_STANDINGARTICLES_OF_INCORPORATIONOTHERGet Payment Corridors
Get all available payment corridors with supported currencies, countries, and fees. Use this to build dynamic currency selection in your application.
Query Parameters
corridors- Array of all available corridors with fees and railscurrencies.source- All source currencies availablecurrencies.destination- All destination currencies availabletotal_count- Total number of corridors returned
List Customers
Retrieve a paginated list of all customers under your partner account.
Query Parameters
Get Customer Accounts
Retrieve all accounts (virtual banks, virtual wallets, external banks, external wallets) for a specific customer.
Path Parameters
Get Customer Transactions
Retrieve all transactions for a specific customer.
Path Parameters
Get Customer Quotes
Retrieve all quotes created for a specific customer.
Path Parameters
Get Account
Retrieve details of a specific account by its ID.
Path Parameters
Update Account
Update an existing account's information. For virtual bank accounts you can also change the liquidation target (the crypto wallet that receives deposits) without recreating the account — pass a liquidation block: {"liquidation": {"enabled": true, "accountId": "<walletAccountId>", "targetCurrency": "USDT"}}. Flat aliases (liquidation_account_id + target_currency) and the gateway-native liquidationInformation shape are also accepted. The wallet must be one of the same customer's own crypto wallets. Requests with no updatable fields return 400. Successful updates emit an account.updated webhook event to your subscribed endpoints.
Path Parameters
Request Body
Delete Account
Delete an existing account. This action cannot be undone.
Path Parameters
Create Virtual Bank Account
Create a virtual bank account for receiving fiat deposits. Customer must have completed KYC verification.
Request Body
Liquidation Wallet *
Incoming fiat deposits will be automatically converted to crypto and sent to this wallet.
GET /api/trustodi/v1/customers/{customer_id}/accounts (the wallet's accountId).
MISSING_LIQUIDATION_ACCOUNT). AED liquidation is optional. EUR supports both USDC and USDT.
Sandbox limitation: Only EUR virtual bank accounts can be created in the sandbox environment. USD/GBP/AED sandbox requests are rejected with
SANDBOX_CURRENCY_UNSUPPORTED; those currencies are available in production.
Accepted field aliases: Instead of the nested
liquidation: {"enabled": true, "accountId": "...", "targetCurrency": "USDC"} object you may send the flat fields liquidation_account_id and target_currency (or camelCase liquidationAccountId / targetCurrency). partner_markup_percent is accepted as an alias of partnerMarkup. The nested shape wins if both are sent.
Account ID format: The response returns the account's canonical ID (e.g.
cmftxq2ab001lju0asfront3q). Use it in all subsequent GET / DELETE / payment calls. Previously issued IDs in other formats keep working.
Get Virtual Bank Account
Retrieve details of a virtual bank account.
Path Parameters
Create Virtual Wallet
Create a virtual cryptocurrency wallet (deposit address) for a customer to receive crypto deposits.
Request Body
Get Virtual Wallet
Retrieve details of a virtual wallet.
Path Parameters
Create External Bank Account
Register an external bank account for receiving off-ramp payouts. This is the customer's own bank account where they will receive fiat withdrawals.
Request Body
Bank / Account Holder Address *
Bank networks reject placeholder addresses like "N/A" or "Default Address" — please enter a real address.Account ID format: The response returns the account's canonical ID (e.g.
cmftxq2ab001lju0asfront3q). Use it in all subsequent GET / DELETE / payment calls. Previously issued IDs in other formats keep working.
Create External Wallet
Register an external crypto wallet for receiving on-ramp payouts. This is the customer's own wallet where they will receive crypto.
Request Body
Account ID format: The response returns the wallet's canonical ID (e.g.
cmftxq2ab001lju0asfront3q). Use it in all subsequent GET / DELETE / payment calls. Previously issued IDs in other formats keep working.
Create Payment
Create a new payment. Uses the same flow as the webapp. Select payment type below to use the appropriate endpoint.
Payment Type
Payment Mode
Common Fields
Quote Details
-
-
-
-
On-ramp: Fiat to Crypto
Off-ramp: Crypto to Fiat
Crypto → Crypto Payout
Pay out stablecoins from a customer's virtual crypto wallet to an external wallet address. Both legs must be stablecoins — only USDC and USDT are supported as source and destination currencies. This is a pure crypto-to-crypto transfer (not a swap and not a fiat off-ramp). Create a Quote first, then create the payout within 60 seconds using the returned quote_id.
⛽ Gas tokens: on-chain transfers require native gas tokens (e.g. SOL on Solana, ETH on Ethereum) in the sending wallet to cover network fees. Ensure the wallet holds enough of the network's native token before sending, or the transaction may fail.
Required Fields
Source (virtual crypto wallet)
Destination (external wallet)
USDC and USDT are accepted on either leg — any other currency is rejected with UNSUPPORTED_DESTINATION. The source must be one of the customer's virtual crypto wallets. Quote binding is strict (INVALID_QUOTE, QUOTE_EXPIRED, QUOTE_CUSTOMER_MISMATCH). A single payment.created webhook is emitted on success.
Get Payment Status
Retrieve the current status and details of a payment.
Path Parameters
Get Crypto Addresses
Generate crypto deposit addresses for receiving payments. Returns wallet addresses for supported cryptocurrencies and networks.
Request Body
List Payments
Retrieve a paginated list of all payments with optional status filtering.
Query Parameters
Cancel Payment
Cancel a pending payment. Only payments that have not been processed can be cancelled.
Path Parameters
List Transactions
Retrieve a paginated list of all transactions.
Query Parameters
Get Transaction
Retrieve details of a specific transaction.
Path Parameters
Preview Quote
Preview a quote without creating it. Useful for showing estimated costs to users.
Request Body
Create Quote
Create a quote that can be used for payment. Uses the same flow as the webapp. Quotes expire after 60 seconds. Requires a valid customer ID.
Request Body
Get Quote
Retrieve details of a specific quote by its ID.
Path Parameters
Refresh Quote
Refresh an expired quote to get updated rates.
Request Body
Webhook Portal
Manage endpoints, inspect deliveries, replay eventsTRUSTODI delivers real-time event notifications (customer KYC updates, payment status changes, account events) to webhook endpoints you register. Sandbox and production are fully isolated — each opens its own portal scoped to that environment.
Sandbox
TEST MODETest webhook delivery with mock events. No real customer data — safe to experiment.
Production
LIVEReceive real production events for your live customers. Configure signing secrets and retry policies.
Or manage endpoints over the API
All three endpoints share the same authentication as the rest of the white-label API and operate on the environment matched by your API key (sandbox vs production).
POST /api/trustodi/v1/webhooks— register a new endpointGET /api/trustodi/v1/webhooks— list your endpointsDELETE /api/trustodi/v1/webhooks/:webhookId— remove an endpoint
Event catalog
These are the events you can subscribe to today. Each event delivers the common envelope plus one entity object (see Sample payloads below).
customer.created— fires when a new customer is created under your partner account.customer.updated— fires when a customer record changes, including KYC outcomes, profile edits, or status changes.customer.deleted— fires when a customer record is deleted/archived; carries the final known customer details plusdeleted_at.
account.created— fires when a new account (bank or wallet) is provisioned for one of your customers.account.updated— fires when an account record changes (status, balance, or metadata).account.deleted— fires when an account (bank or wallet) is deleted/archived; carries the final known account details plusdeleted_at.
payment.created— fires when a new payment is created (onramp, offramp, or move-money).payment.updated— fires when a payment transitions to a new status (e.g. completed or failed).payment.deleted— fires when a payment record is deleted/voided; carries the final known payment details plusdeleted_at.
pay.payment.confirmed— fires when one of your Trustodi Pay crypto checkouts (a payment link or an invoice) is confirmed on-chain. Fires once per confirmed payment, including overpaid payments and subscription-cycle invoices.
Event families for crypto trading & invest (transaction.*), staking (staking.*), and external-wallet whitelisting (external_wallet.whitelist.*) are part of an integration that has not launched yet. They are listed here for reference only and are not available to subscribe to today. Don't build against them until they're announced as live.
Sample payloads
Every delivery wraps a common envelope around one entity object. The envelope fields are always present:
{
"event": "customer.created", // the event type
"partner_id": "AFF008", // your partner ID
"environment": "production", // "sandbox" or "production"
"timestamp": "2026-06-12T07:00:00.000Z"
// ...plus one entity object (see below)
}
These are the exact shapes you receive. Fields are omitted when they have no value, so treat every field except the "always present" ones as optional and code defensively. Identify records by the TRUSTODI id (id / account_id), never by any provider id.
customer.created — always present: id, type, kyc_status
{
"event": "customer.created",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T07:00:00.000Z",
"customer": {
"id": "CUST_1700000000000",
"external_id": "your-own-ref-123",
"name": "Jane Doe",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"type": "INDIVIDUAL",
"kyc_status": "NOT_STARTED",
"kyc_url": "https://app.trustodi.com/kyc/abc123"
}
}
customer.updated — fires on KYC status changes; carries updated_at
{
"event": "customer.updated",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T08:00:00.000Z",
"customer": {
"id": "CUST_1700000000000",
"external_id": "your-own-ref-123",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"country": "US",
"type": "INDIVIDUAL",
"kyc_status": "APPROVED",
"updated_at": "2026-06-12T08:00:00.000Z"
}
}
account.created (virtual bank) — always present: account_id, customer_id, account_type, currency. Virtual accounts also carry a developer_fee block — your fee on deposits to the account. When your corridor pricing records your negotiated cost basis, this is your markup share (the all-in rate minus your cost); otherwise it is the fee stamped at creation: variable_fee (a fraction, 0.01 = 1%), fixed_fee (USD per deposit), source (corridor = your negotiated per-corridor rate, formula = standard pricing) and corridor_key when the rate is corridor-based. External accounts never include it.
{
"event": "account.created",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T07:00:00.000Z",
"account": {
"account_id": "vba_1700000000000",
"customer_id": "CUST_1700000000000",
"account_type": "VIRTUAL_BANK",
"currency": "EUR",
"nickname": "EUR payouts",
"account_holder": "Acme Ltd",
"bank_name": "Example Bank S.A.",
"iban": "DE89370400440532013000",
"account_number": "0532013000",
"routing_number": "026073150",
"sort_code": "04-00-04",
"swift_code": "EXMPDEFF",
"bank_address": "1 Example Street, Berlin",
"status": "ACTIVE",
"is_virtual": true,
"developer_fee": {
"variable_fee": 0.01,
"fixed_fee": 0.5,
"source": "corridor",
"corridor_key": "EUR:USDC:SEPA:DE"
}
}
}
account.created (wallet) — wallets carry address + chain instead of bank fields
{
"event": "account.created",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T07:00:00.000Z",
"account": {
"account_id": "wlt_1700000000000",
"customer_id": "CUST_1700000000000",
"account_type": "VIRTUAL_WALLET",
"currency": "USDC",
"chain": "SOLANA",
"address": "7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2",
"status": "ACTIVE",
"is_virtual": true,
"developer_fee": {
"variable_fee": 0.01,
"fixed_fee": 0,
"source": "formula"
}
}
}
account.updated — carries updated_at; virtual accounts keep their developer_fee block
{
"event": "account.updated",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T09:00:00.000Z",
"account": {
"account_id": "vba_1700000000000",
"customer_id": "CUST_1700000000000",
"account_type": "VIRTUAL_BANK",
"currency": "EUR",
"nickname": "EUR payouts (updated)",
"is_primary": true,
"updated_at": "2026-06-12T09:00:00.000Z"
}
}
payment.created — full source/destination breakdown plus customer/account attribution: customer_id is your customer the payment belongs to, and source_account_id/destination_account_id identify the account on each leg (for a virtual-account deposit, source_account_id is the virtual account the funds arrived on). amount + currency always refer to the destination leg; always present: id, reference, type, status
{
"event": "payment.created",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T10:00:00.000Z",
"payment": {
"id": "TRX-ABC123",
"reference": "TRX-ABC123",
"type": "onramp",
"status": "PENDING",
"customer_id": "CUST_1700000000000",
"source_currency": "USD",
"source_amount": 100,
"source_account_id": "vba_1700000000000",
"source_rail": "LOCAL",
"destination_currency": "USDC",
"destination_amount": 99.5,
"destination_account_id": "vw_1700000000000",
"destination_rail": "SOLANA",
"network": "SOLANA",
"quote_id": "quote_abc123",
"total_fees": 0.5,
"exchange_rate": 1.0,
"amount": 99.5,
"currency": "USDC"
}
}
payment.updated — fires on every status change; carries the same source/destination breakdown and customer/account attribution (customer_id, source_account_id, destination_account_id, rails, network) as payment.created when known. amount + currency always refer to the destination leg. failure_reason/failure_description appear on failure. When known, payments also carry richer detail: lifecycle timestamps, the pricing quote, your developer_fee breakdown (total is the all-in fee charged on the payment), rail tracking references (imad/omad/trace_number/uetr/transaction_hash), fuller legs (source_chain/source_address/source_iban/source_account_number and the destination equivalents), plus sender_name, payment_reason and status_reason. All of these are conditional — they appear once the rails report them.
{
"event": "payment.updated",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T10:05:00.000Z",
"payment": {
"id": "TRX-ABC123",
"reference": "TRX-ABC123",
"type": "onramp",
"status": "COMPLETED",
"customer_id": "CUST_1700000000000",
"source_currency": "USD",
"source_amount": 100,
"source_account_id": "vba_1700000000000",
"source_rail": "LOCAL",
"sender_name": "Jane Doe",
"destination_currency": "USDC",
"destination_amount": 99.5,
"destination_account_id": "vw_1700000000000",
"destination_rail": "SOLANA",
"destination_chain": "SOLANA",
"destination_address": "7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2",
"network": "SOLANA",
"amount": 99.5,
"currency": "USDC",
"exchange_rate": 1.0,
"total_fees": 0.5,
"lifecycle": {
"created_at": "2026-06-12T10:00:00.000Z",
"completed_at": "2026-06-12T10:05:00.000Z"
},
"quote": { "exchange_rate": 1.0, "expires_at": "2026-06-12T10:15:00.000Z" },
"developer_fee": { "total": 0.5, "variable_fee": 0.005 },
"tracking": { "imad": "20260612ABC123", "trace_number": "021000021234567" }
}
}
customer.deleted — the final known customer details plus deleted_at
{
"event": "customer.deleted",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T11:00:00.000Z",
"customer": {
"id": "CUST_1700000000000",
"external_id": "your-own-ref-123",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"country": "US",
"type": "INDIVIDUAL",
"kyc_status": "APPROVED",
"deleted_at": "2026-06-12T11:00:00.000Z"
}
}
account.deleted — the final known account details plus deleted_at
{
"event": "account.deleted",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T11:05:00.000Z",
"account": {
"account_id": "vba_1700000000000",
"customer_id": "CUST_1700000000000",
"account_type": "VIRTUAL_BANK",
"currency": "EUR",
"status": "DELETED",
"is_virtual": true,
"iban": "DE89370400440532013000",
"deleted_at": "2026-06-12T11:05:00.000Z"
}
}
payment.deleted — the final known payment details plus deleted_at
{
"event": "payment.deleted",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T11:10:00.000Z",
"payment": {
"id": "TRX-ABC123",
"reference": "TRX-ABC123",
"type": "onramp",
"status": "DELETED",
"customer_id": "CUST_1700000000000",
"source_currency": "USD",
"source_amount": 100,
"destination_currency": "USDC",
"destination_amount": 99.5,
"amount": 99.5,
"currency": "USDC",
"deleted_at": "2026-06-12T11:10:00.000Z"
}
}
pay.payment.confirmed — a Trustodi Pay crypto checkout was confirmed on-chain; always present: id, reference, type, status, kind
{
"event": "pay.payment.confirmed",
"partner_id": "AFF008",
"environment": "production",
"timestamp": "2026-06-12T10:05:00.000Z",
"pay_payment": {
"id": "TRX_1700000000000",
"reference": "PAY-ABC123",
"type": "pay_collection",
"kind": "invoice",
"status": "PAID",
"invoice_id": "PAYI_1700000000000",
"amount": 100,
"currency": "USD",
"crypto_amount": 100,
"crypto_currency": "USDC",
"chain": "SOLANA",
"tx_hash": "5x...abc",
"confirmed_at": "2026-06-12T10:05:00.000Z"
}
}
Verifying deliveries
Every webhook delivery is signed. When you register an endpoint, the response includes a signing secret (whsec_…) shown only once — store it securely. Each request carries signature headers:
webhook-id— unique message IDwebhook-timestamp— Unix send time (reject deliveries that are too old to prevent replay)webhook-signature— the signature to compare against the secret
Verify the signature on every request before trusting it. Our delivery is built on the open Standard Webhooks signature scheme, and the official verification libraries (available for most languages) check the headers for you in one call — pass the raw request body and your signing secret.
Integration notes
- KYC outcomes arrive as
customer.updated— there is no separatekyc.*event. Watch thekyc_statusfield on the customer object to track approval. - TRUSTODI-owned statuses can appear in
kyc_statusand take precedence over the verification lifecycle values:rejected_offboarded(the account has been closed),more_information_needed(a compliance review is open and the customer has been asked for documents/information), andunder_review(the requested information has been submitted; the review is in progress until released or the verification provider approves/rejects). Precedence:rejected_offboarded>more_information_needed/under_review> KYC status. Acustomer.updatedevent fires on every status transition, including when the customer submits the requested information. - Correlate on our customer ID (e.g.
CUST_…), not the underlying provider's ID. The provider's ID can change and should not be used as your key. - Sandbox and production are fully isolated — each environment has its own endpoints and signing secrets, and every payload carries
environmentso you can route accordingly. - Delivery is at-least-once — failed deliveries are retried, so the same event may arrive more than once. Make your handlers idempotent (de-duplicate on the
webhook-idheader or the entity ID + status). - Some events cannot be fully exercised in sandbox. Deposit-driven events depend on real money movement, so in sandbox you can trigger
customer.*,account.created/updatedandpayment.createdby calling the API, but apayment.updatedthat reachesCOMPLETED— and bank details that only populate after the account is fully provisioned (e.g.iban) — require a real deposit and therefore appear in production. Build against the documented shapes above; the field set is identical across environments.
Register Webhook
Register a new webhook endpoint to receive event notifications.
Request Body
whsec_…). Store it securely — it can't be retrieved again — and use it to verify the signature on every delivery (see Verifying deliveries below).
List Webhooks
Retrieve a list of all registered webhooks.
Delete Webhook
Delete an existing webhook registration.
Path Parameters
Get Corridors
Retrieve a list of all available payment corridors. Corridors define the supported source and destination currency pairs for payments.
Invest: Get Swap Quote
Get a short-lived Invest swap quote. Invest is a crypto-to-crypto product only. The full list of supported assets is sourced live from the trading desk for your environment — call GET /api/partner/invest/config (or just open the asset pickers below, which load it automatically). It spans the major coins plus a wide range of altcoins; the exact set can vary by environment. Any request that includes a fiat leg (USD, EUR, GBP, AED, …) or a fiat-to-fiat pair is rejected with UNSUPPORTED_ASSET. For fiat conversions use the onramp / offramp / move-money endpoints instead.
invest/quote to get a single-use quote_id, indicative buy_amount, effective fee_bps and expires_at, then pass that quote_id to invest/execute. Pricing is a fixed desk fee of 12 bps plus your configured Invest markup; the effective total is returned as fee_bps (100 bps = 1.00%) with your portion broken out as partner_markup_bps. The customer_id must belong to you and be KYC-approved. This route is also available under /api/partner/v1/{clientId}/payments/invest/quote and (for partner-dashboard sessions) at POST /api/partner/customers/:customerId/invest/quote.
Request Body
quote_id- Single-use id passed to executebuy_amount- Amount ofbuy_assetthe customer receivesrate_display/fill_price- Human-readable rate and execution pricenotional_usd- USD notional of the tradefee_bps/fee_usd- Effective total fee (desk + your markup)partner_markup_bps- Your portion of the fee, in bpsexpires_at/ttl_seconds- Quote expiry timestamp and remaining seconds
Invest: Execute Swap
Execute an Invest swap using the quote_id returned by invest/quote. The quote is single-use and must not be expired.
quote_id below. destination is optional — leave it blank to settle into TRUSTODI custody. A successful execute emits the transaction.invest_swap.created webhook event; settlement progress is delivered through your configured webhook subscriptions. Also available at POST /api/partner/customers/:customerId/invest/execute.
Request Body
transaction_id/reference- The created Invest transaction idstatus- Initial status; progress is delivered via webhooksell_asset/buy_asset/sell_amount/buy_amountfee_bps/fee_usd/partner_markup_bps
Invest: Buy (Fiat → Crypto)
Submit a fiat → crypto buy to the TRUSTODI Invest desk in a single call (no separate quote/execute step). Settlement is processed by the desk; status updates are delivered via webhook.
customer_id, source_currency, source_amount, target_asset. Optional: withdraw_to_wallet, source_vbank_account_id. The customer_id must belong to you and be KYC-approved. Also available at POST /api/partner/customers/:customerId/invest/buy.
Request Body
{ success, data: { transaction_id, reference, type: "invest_buy", customer_id, ... }, poweredBy }
Invest: Sell (Crypto → Fiat)
Submit a crypto → fiat sell to the TRUSTODI Invest desk in a single call. Fiat proceeds settle to the customer's nominated bank account; status updates are delivered via webhook.
customer_id, asset, quantity, fiat_currency, destination_bank_account_id. The destination bank account must belong to the customer. Also available at POST /api/partner/customers/:customerId/invest/sell.
Request Body
{ success, data: { transaction_id, reference, type: "invest_sell", customer_id, ... }, poweredBy }
Invest: History
Retrieve your Invest transaction history across all of your customers. This single endpoint returns swap, buy and sell history together — each row carries a type of invest_swap, invest_buy or invest_sell.
partner_commission) and never exposes internal revenue. For a single customer, use GET /api/partner/customers/:customerId/invest/history.
history- Array of Invest transactions, newest firsttype-invest_swap|invest_buy|invest_sellsell_asset/buy_asset/sell_amount/buy_amountfee_usd/fee_bps/partner_markup_bps/partner_commissionnotional_usd/status/created_at
Staking: List Instruments
List the assets your customers can stake, each with the net APR, minimum stake, lockup and unbonding terms offered by the TRUSTODI Invest desk. No request body required.
GET /api/partner/invest/staking/instruments for partner-dashboard sessions.
{ success, data: [ { asset, net_apr, min_stake, lockup_days, unbond_days, reward_frequency } ], poweredBy }
Staking: Open Position
Open a staking position for a customer at the current net APR for the chosen asset. The staked quantity must be at or above the asset's minimum stake (see List Instruments).
customer_id, asset, quantity. The customer_id must belong to you and be KYC-approved. Also available at POST /api/partner/customers/:customerId/invest/staking/open.
Request Body
{ success, data: { position_id, status, asset, quantity, net_apr, unbond_days, opened_at, customer_id }, poweredBy }
Staking: List Positions
List the staking positions held by one of your customers, including accrued and paid rewards. The customer_id is passed as a query parameter.
GET /api/partner/customers/:customerId/invest/staking/positions for partner-dashboard sessions.
Query Parameters
{ success, data: [ { position_id, asset, quantity, net_apr, status, rewards_accrued, rewards_paid, unbond_days, opened_at } ], poweredBy }
Staking: Unstake
Request an unstake against an existing position. Omit quantity (or set it to the full staked amount) for a full exit; provide a smaller quantity for a partial unstake. The position enters its unbonding period before funds become available.
customer_id, position_id. Optional: quantity (partial unstake). Also available at POST /api/partner/customers/:customerId/invest/staking/unstake.
Request Body
{ success, data: { position_id, status, asset, unbond_days, customer_id, ... }, poweredBy }
List Pay Clients
List the clients (payers) saved in your Trustodi Pay account. Results are always scoped to your partner account.
/api/partner/v1/{clientId}/pay/clients. Both v1 and v2 resolve to the same handler.
Query Parameters
{ success: true, data: [ { id, email, name, phone, wallet_address, tags, is_recurring, created_at, ... } ] }
Create Pay Client
Create a client (payer) record in your Trustodi Pay account.
email. Optional: name, phone, walletAddress, tags (array), isRecurring (bool), externalPayId. Also available at /api/partner/v1/{clientId}/pay/clients.
Request Body
Get Pay Client
Retrieve a single Trustodi Pay client by ID.
Path Parameters
Update Pay Client
Update an existing Trustodi Pay client. Send only the fields you want to change.
Path Parameters
Request Body
Delete Pay Client
Delete a Trustodi Pay client by ID. Returns { deleted: true } on success.
Path Parameters
Get Checkout Networks
Return the checkout network configuration for a Pay client. The response lists every supported
network (Solana, Ethereum, Polygon, Base) and stablecoin (USDC, USDT) with its enabled,
provisioned and supported flags, plus a top-level walletReady
indicator. Buyers are only offered pairs that are both enabled and provisioned. Pass
?environment=sandbox for sandbox configuration (defaults to production).
Requires the api_access capability. Also available at
/api/partner/v1/{clientId}/pay/clients/:id/accepted-networks; both v1 and
v2 resolve to the same handler.
Path Parameters
Query Parameters
Update Checkout Networks
Enable or disable network + stablecoin pairs for a Pay client's checkout. Enabling a pair for the
first time automatically provisions its dedicated collection wallet, so the response echoes the
refreshed configuration plus an errors array describing any pair that could not be
enabled (for example an unsupported combination, or wallet setup not yet complete). The
networks body is a map of { NETWORK: { USDC: true|false, USDT: true|false } }.
Pairs you omit are left unchanged. Requires the api_access capability.
Path Parameters
Body Parameters
List Pay Products
List the products in your Trustodi Pay catalog.
/api/partner/v1/{clientId}/pay/products.
Query Parameters
{ success: true, data: [ { id, name, description, price, currency, type, image_url, is_active, ... } ] }
Create Pay Product
Create a product in your Trustodi Pay catalog. Products can be reused across payment links and invoices.
name. Optional: description, price (number), currency (default USD), type (FIXED | VARIABLE, default FIXED), imageUrl, isActive (bool, default true).
Request Body
Get Pay Product
Retrieve a single Trustodi Pay product by ID.
Path Parameters
Update Pay Product
Update an existing product. Send only the fields you want to change.
Path Parameters
Request Body
Delete Pay Product
Delete a Trustodi Pay product by ID. Returns { deleted: true } on success.
Path Parameters
List Payment Links
List the shareable payment links in your Trustodi Pay account.
Query Parameters
{ success: true, data: [ { id, short_code, url_slug, amount, currency, description, is_active, product_id, crypto_currency, crypto_chain, ... } ] }
crypto_currency & crypto_chain identify the network the buyer pays on and funds collect to (e.g. USDC on SOLANA).
Create Payment Link
Create a shareable payment link. The same endpoint supports three creation modes, all producing an identical link:
- From a product — pass
productIdto inherit its price, currency, and description. - From an invoice — pass
invoiceIdto inherit the invoice's amount, currency, and conversion details. - From a new price — set
amountandcurrencydirectly for an ad-hoc charge.
currency) must be a supported off-ramp fiat currency (e.g. USD, EUR, GBP, AED) and is required whenever an amount is set — it is never left blank. The buyer always pays in a stablecoin (USDC or USDT); no other send currency is accepted.
Optional:
productId, invoiceId, amount (number), currency, description, shortCode (auto-generated if omitted), isActive (bool). The response includes a short_code usable on the public checkout.
Network. Optionally pass
cryptoCurrency (USDC/USDT) and cryptoChain (e.g. SOLANA) to choose the network the buyer pays on and where funds collect; defaults to USDC on SOLANA. These are echoed back as crypto_currency / crypto_chain.
Request Body
Get Payment Link
Retrieve a single payment link by ID.
Path Parameters
Deactivate Payment Link
Deactivate a payment link so it can no longer be paid. Returns the updated link with is_active: false.
Path Parameters
List Invoices
List the invoices in your Trustodi Pay account, optionally filtered by status.
Query Parameters
{ success: true, data: [ { id, invoice_number, amount, currency, vat_amount, status, short_code, due_date, crypto_currency, crypto_chain, ... } ] }
crypto_currency & crypto_chain identify the network the buyer pays on and funds collect to (e.g. USDC on SOLANA).
Create Invoice
Create an invoice. The response includes a short_code that can be used on the public checkout to collect payment.
currency is the amount to receive and must be a supported off-ramp fiat currency (e.g. USD, EUR, GBP, AED). It is required on every invoice — never left blank. The buyer settles the invoice in a stablecoin (USDC or USDT).
Required:
amount, currency. Optional: customerId, productId, invoiceNumber (auto-generated if omitted), dueDate (YYYY-MM-DD), vatAmount.
Network. Optionally pass
cryptoCurrency (USDC/USDT) and cryptoChain (e.g. SOLANA) to choose the network the buyer settles on and where funds collect; defaults to USDC on SOLANA. These are echoed back as crypto_currency / crypto_chain.
Request Body
Get Invoice
Retrieve a single invoice by ID.
Path Parameters
List Subscriptions
List the recurring subscriptions in your Trustodi Pay account, optionally filtered by status.
active and advances the billing period.
Query Parameters
{ success: true, data: [ { id, amount, currency, interval, interval_count, status, current_period_start, current_period_end, next_billing_at, current_invoice_id, cycles_completed, ... } ] }
Create Subscription
Create a recurring subscription. The first invoice is issued immediately; subsequent invoices are issued automatically each cycle.
currency is the amount to receive (a supported off-ramp fiat such as USD, EUR, GBP, AED) and the buyer settles each cycle's invoice in a stablecoin (USDC/USDT). For non-USD currencies attach cryptoAmount + exchangeRate from a quote.
Required:
amount, currency, interval (daily|weekly|monthly|yearly). Optional: intervalCount (default 1), graceDays (default 7), customerId, description, amountMode (receive|send).
Paid plan required. Creating subscriptions requires an active Pay plan; the scheduler also skips subscriptions whose owner's plan is inactive.
Request Body
Get Subscription
Retrieve a single subscription by ID.
Path Parameters
Cancel Subscription
Cancel a subscription. No further cycles are billed; any open (unpaid) invoice for the current cycle is voided.
Path Parameters
List Settlement Bank Accounts
List the bank accounts where your Trustodi Pay payouts settle.
{ success: true, data: [ { id, bank_name, account_number, holder_name, currency, country_code, is_default, ... } ] }
Create Settlement Bank Account
Add a bank account to receive Trustodi Pay payouts.
bankName, accountNumber, holderName. Optional: currency, countryCode (ISO 3166-1 alpha-2), isDefault (bool).
Request Body
Delete Settlement Bank Account
Remove a settlement bank account by ID. Returns { deleted: true } on success.
Path Parameters
List Payouts
List your Trustodi Pay payouts (settlement batches).
Query Parameters
{ success: true, data: [ { id, bank_account_id, status, total_amount, fee_amount, net_payout, currency, crypto_currency, crypto_chain, ... } ] }
crypto_currency & crypto_chain identify the network the payout was settled from (e.g. USDC on SOLANA); also available under metadata.
Preview Payout Fee
Preview the fee and net amount for a payout before creating it.
amount. Returns { totalAmount, feeAmount, netPayout, feeRate }.
Request Body
Create Payout
Create a payout (settlement batch) to one of your settlement bank accounts. The payout starts in PENDING status.
currency, it must match the bank account's currency or the request is rejected. The currency is therefore best omitted.
Required:
amount, bankAccountId. Optional: currency (must match the bank account currency if supplied).
Request Body
Public Checkout
Resolve a public checkout by its short code. This endpoint is unauthenticated — it powers the hosted checkout page your payers see. The short code comes from an invoice or a payment link.
Path Parameters
{ success: true, data: { kind: "invoice" | "payment_link", target: { ... }, product: { ... }, branding: { displayName, primaryColor, logoUrl, supportEmail } } }
Messaging Status
Reports whether messaging is available for your account. Messaging lets you connect your own WhatsApp, Telegram or email account and send payment links, invoices and subscription reminders to your customers directly from you. Requires an active Trustodi Pay plan.
{ success: true, data: { configured: true, plan_active: true, channels: ["whatsapp","telegram","email"] } }
List Connections
Lists your connected messaging channels. Each entry reports the channel, whether it is connected, and a human-readable account label. Connections are isolated to your account.
{ success: true, data: [ { id: "...", channel: "whatsapp", connected: true, account_label: "+1555…", created_at: "..." } ] }
Connect Channel
Starts connecting one of your messaging accounts. Returns a hosted authentication URL — open it to link your WhatsApp, Telegram or email account. The connection becomes active once you finish the hosted flow.
Body Parameters
{ success: true, data: { url: "https://…", channel: "whatsapp" } }
Disconnect Channel
Removes one of your connected messaging channels. After disconnecting, messages and reminders will no longer be delivered over that channel.
Path Parameters
{ success: true, data: { disconnected: true } }
Send Message
Sends a message from your connected channel to a customer. Send free-form text, or reference a payment link or invoice to share its checkout URL automatically. The channel must already be connected.
Body Parameters
paymentLinkId or invoiceId to send a payment resource, or text for a free-form message. Use note to prepend a short message when sending a resource.
{ success: true, data: { id: "...", channel: "whatsapp", status: "sent" } }
Message Log
Returns recent messages sent from your connected channels, including subscription reminders delivered automatically. Use the optional limit query parameter to cap the number of rows.
Query Parameters
{ success: true, data: [ { id, channel, recipient, related_type, status, created_at } ] }
Get Service Fee
Returns your configured Trustodi Pay service fee — your own percentage that is added on top of the buyer's total on every payment link and invoice you create. This fee is separate from the on/off-ramp corridor markup and from any reseller plan markup, and the extra flows to your net payout.
{ success: true, data: { payMarkupPercent: 2.5 } }
Update Service Fee
Sets your Trustodi Pay service fee as a percentage. Accepts 0 to 10 (values outside the range are clamped). The new fee applies to links and invoices created after the change; existing ones keep their already-fixed totals. Each created link/invoice records its base amount and the fee amount for transparency.
Request Body
{ success: true, data: { payMarkupPercent: 2.5 } }
Error Codes
TRUSTODI uses standard HTTP status codes alongside custom error codes for specific failure scenarios. Failed, refunded, or rejected payments include a failure_code, failure_reason, and failure_description in the response.
HTTP Status Codes
| Code | Description |
|---|---|
200 | Success |
201 | Created |
400 | Bad Request - Invalid parameters or missing required fields |
401 | Unauthorized - Invalid or missing API key |
403 | Forbidden - Access denied |
404 | Not Found |
429 | Too Many Requests - Rate limit exceeded |
500 | Internal Server Error |
Validation Error Codes
| Code | Description |
|---|---|
TRUSTODI_MISSING_REQUIRED_FIELD | A required field is missing from the request |
TRUSTODI_INVALID_FIELD | Field value is invalid or in wrong format |
TRUSTODI_INVALID_ENVIRONMENT | Invalid environment specified |
TRUSTODI_AGE_VALIDATION_FAILED | Individual customer age must be between 18 and 65 years old |
TRUSTODI_CONFIG_ERROR | Configuration error for partner |
TRUSTODI_CUSTOMER_CREATION_FAILED | Failed to create customer |
Authentication & Rate Limit Codes
| Code | Description |
|---|---|
TRUSTODI_AUTH_01 | Authentication failed - invalid or missing API key |
TRUSTODI_AUTH_02 | Invalid credentials |
TRUSTODI_KYC_01 | KYC verification required before this operation |
TRUSTODI_LIMIT_01 | Rate limit exceeded |
TRUSTODI_LIMIT_03 | External account deposit limit exceeded |
Payment Failure Codes
Returned in the failure_code field when a payment fails, is refunded, or is rejected.
| Code | Reason | Description |
|---|---|---|
TRUSTODI_PAY_01 | PAYMENT_FAILED | General payment failure |
TRUSTODI_PAY_02 | PAYMENT_CANCELLED | Payment was cancelled |
TRUSTODI_PAY_03 | FUNDING_RECEIVED_AFTER_QUOTE_EXPIRED | Funds were received after the quote expired |
TRUSTODI_PAY_04 | FUNDS_RETURNED_BY_RECEIVING_BANK | Funds returned by the receiving bank |
TRUSTODI_PAY_05 | RFI_NOT_ANSWERED_IN_TIME | Required information not provided in time |
Account Error Codes
| Code | Reason | Description |
|---|---|---|
TRUSTODI_ACCT_01 | ACCOUNT_CREATION_FAILED | Account creation failed |
TRUSTODI_ACCT_02 | INVALID_EXTERNAL_ACCOUNT_NUMBER | Invalid bank account number |
TRUSTODI_ACCT_03 | INVALID_EXTERNAL_ACCOUNT_HOLDER_NAME | Invalid account holder name |
TRUSTODI_ACCT_04 | INVALID_EXTERNAL_ACCOUNT_HOLDER_ADDRESS | Invalid account holder address |
TRUSTODI_ACCT_05 | EXTERNAL_ACCOUNT_HOLDER_ADDRESS_IS_PMB_OR_PO_BOX | Account holder address cannot be a PO Box or PMB |
Webhook Error Codes
| Code | Description |
|---|---|
TRUSTODI_HOOK_01 | Webhook delivery failed |