Withdrawals

Create customer withdrawal

Create a fiat withdrawal to a customer's registered bank.

POST
/api/v2/customers/{customerId}/withdrawal

Creates a withdrawal to the customer's registered bank account.

Prerequisites

API withdrawals must be enabled for your organization. If not enabled, this endpoint returns 403 Forbidden. Contact support to enable API withdrawals.

Execution Modes

  1. Automatic Execution (PROCESSING) — Withdrawal executes immediately when:

    • Sufficient available allowance
    • Sufficient USDC balance in wallet
  2. Manual Approval (PROPOSED) — Transaction created for dashboard approval when:

    • Withdrawal exceeds available allowance
    • Insufficient USDC balance in wallet (user can deposit funds before signing)
    • Automatic execution fails (transaction converted to manual approval)

Custodial Balances

USDT and EURC (and USDC when source is custodial) are held on the customer's custodial balance rather than the wallet. These withdrawals are debited and paid out immediately (PROCESSING) when the organization has custodial balances and API withdrawals enabled. There is no manual-approval fallback: an insufficient custodial balance returns 400, and a disabled feature returns 403.

Amount Specification

Specify the withdrawal amount using one of:

  • sourceAmount: Amount in the source currency to withdraw
  • targetAmount: Amount to receive in the bank's currency

The target currency is automatically determined by the withdrawal bank's configured currency.

Idempotency

Use the idempotencyKey to safely retry requests. Duplicate requests with the same key return the original transaction without creating a new one.

Authorization

bearerAuth
AuthorizationBearer <token>

Organization API key obtained on Settings > API Keys page in Compose UI.

In: header

Path Parameters

customerId*string

Unique identifier of the customer

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST 'https://compose.finance/api/v2/customers/{customerId}/withdrawal' \  -H 'Authorization: Bearer YOUR_API_KEY' \  -H 'Content-Type: application/json' \  -d '{    "withdrawalBankId": "bank_123abc",    "idempotencyKey": "550e8400-e29b-41d4-a716-446655440001",    "targetAmount": "850"  }'

{
  "id": "txn_abc123",
  "type": "WITHDRAWAL",
  "status": "PROCESSING",
  "requiresUiAction": false,
  "paymentRail": "SEPA",
  "createdAt": "2024-01-15T10:30:00.000Z",
  "completedAt": null,
  "reference": "txn_abc123",
  "sourceCurrency": "USDC",
  "targetCurrency": "EUR",
  "sourceAmount": "1000",
  "targetAmount": "865.50",
  "exchangeRate": "0.865",
  "fee": "2.5",
  "withdrawalBank": {
    "id": "bank_123abc",
    "beneficiaryName": "JOHN DOE",
    "currency": "EUR",
    "paymentRail": "SEPA",
    "iban": "DE89370400440532013000",
    "bic": "COBADEFFXXX"
  },
  "walletAddress": null,
  "developerFee": null,
  "txHash": null
}

{
  "error": "Provide either 'sourceAmount' or 'targetAmount' (amount in bank currency)"
}

{
  "error": "Unauthorized - Invalid or missing API key or session"
}

{
  "error": "API withdrawals are not enabled for this organization"
}

{
  "error": "Customer not found"
}

{
  "error": "Failed to execute withdrawal"
}

Withdrawal Status Changed Webhook

Triggered when withdrawal status changes (PROCESSING, PROPOSED, PARTIALLY_SIGNED, COMPLETED, FAILED, CANCELLED, EXPIRED).

Create customer withdrawal bank POST

Adds a new bank account for withdrawals. The customer must be KYC verified. **Currency and payment rails** - `SEPA` (EUR): provide `currency: "EUR"`, `paymentRail: "SEPA"`, `iban`, and `bic`. - `FEDWIRE` (USD): provide `currency: "USD"`, `paymentRail: "FEDWIRE"`, `accountNumber`, and `routingNumber`. - `SWIFT` (USD): provide `currency: "USD"`, `paymentRail: "SWIFT"`, `accountNumber` (or IBAN), `bic`, `bankName`, `bankAddress`, and `bankCountry`. Legacy requests may still send `currency` without `paymentRail`; `EUR` maps to SEPA and `USD` maps to FEDWIRE. **Recipient Types** - `CUSTOMER` - The customer themselves. Auto-approved if the beneficiary name matches the customer's verified name (KYC name for individuals, organization name for corporates). Returns 400 on name mismatch. - `OTHER_INDIVIDUAL` - A third-party individual. Requires manual approval. - `OTHER_BUSINESS` - A third-party business. Requires manual approval. If `recipientType` is not specified, it defaults to `CUSTOMER`. **Address** For `CUSTOMER` recipients, `addressLine1` and `city` are optional. If omitted, the address is auto-resolved from the customer's KYC data (individuals) or stored business address (corporates). You can check the available address via `GET /customers/{customerId}/kyc/address`. For `OTHER_INDIVIDUAL` and `OTHER_BUSINESS`, address fields are required. **Validation** - EUR: IBAN and BIC/SWIFT formats are validated automatically - USD Fedwire: account number and routing number are validated automatically - USD SWIFT: BIC/SWIFT format is validated automatically, and bank name/address/country are required - For CUSTOMER: beneficiary name is checked against the customer's verified identity (individual) or organization name (corporate)