Create customer withdrawal
Create a fiat withdrawal to a customer's registered bank.
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.
This endpoint requires an API key. Browser-session withdrawals must use the dashboard approval flow.
Execution Modes
-
Automatic Execution (
PROCESSING) — Withdrawal executes immediately when:- Sufficient available allowance
- Sufficient USDC balance in wallet
-
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)
PROPOSED is only used when the on-chain allowance module is configured. If API withdrawals are enabled but the module or delegate is not configured on your Safe, or the allowance cannot be read, the request fails with 500 {"error": "Failed to execute withdrawal"}. If your organization has no Safe wallet, wallet USDC withdrawals fail with 404 (custodial withdrawals do not need a Safe). GET /customers/withdrawal/allowance returns enabled: false in these cases.
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.
AUD Withdrawals
AUD-only customers withdraw to their AUD withdrawal banks (paymentRail: "AU_BANK_TRANSFER") from the wallet or a custodial balance when AUD functionality is enabled for your organization; the customer does not need to be KYC verified, and the account must be active. The withdrawal stays PROCESSING until the bank payment settles. A withdrawal that reached COMPLETED can later move to FAILED if the receiving bank returns the payment; a withdrawal.status_changed webhook is sent. Standard customers cannot withdraw to AUD withdrawal banks, and AUD-only customers still get 400 for EUR and USD accounts. AUD withdrawals are funded with USDC or USDT only: EURC returns 400, as does an AUD withdrawal bank when AUD functionality is not enabled for your organization.
Amount Specification
Specify the withdrawal amount using one of:
sourceAmount: Amount in the source currency to withdrawtargetAmount: 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. Reusing a key for a different customer returns 409.
Authorization
bearerAuth Organization API key obtained on Settings > API Keys page in Compose UI.
In: header
Path Parameters
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
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": null,
"sourceCurrency": "USDC",
"targetCurrency": "EUR",
"sourceAmount": "1000",
"targetAmount": "862.5",
"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
}{
"message": "Invalid request body.",
"errors": [
{
"code": "custom",
"message": "Provide either 'sourceAmount' or 'targetAmount' (amount in bank currency)",
"path": []
}
]
}{
"error": "Unauthorized - Invalid or missing API key or session"
}{
"error": "API withdrawals are not enabled for this organization"
}{
"error": "Customer not found"
}{
"error": "Idempotency key already used for a different customer"
}{
"message": "Invalid media type."
}{
"error": "Rate limit exceeded. Please try again later."
}{
"error": "Internal server error"
}{
"error": "Exchange rate temporarily unavailable"
}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, except for AUD withdrawal banks. **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`. - `AU_BANK_TRANSFER` (AUD): provide `currency: "AUD"`, `paymentRail: "AU_BANK_TRANSFER"`, `bsb`, `accountNumber`, the payee's `firstName`, `lastName` and `dateOfBirth`, and the full address (`addressLine1`, `city`, `postalCode`, `country`). Only for AUD-only customers, verified or not, when AUD functionality is enabled for your organization. The payee is an individual: `CUSTOMER` and `OTHER_INDIVIDUAL` are active immediately, and `OTHER_BUSINESS` is refused. AUD-only customers cannot add EUR or USD accounts. For backwards compatibility the API still accepts `currency` without `paymentRail` (`EUR` maps to SEPA, `USD` to FEDWIRE), but new integrations must send `paymentRail`. **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. AUD withdrawal banks are not name-checked: they are active immediately. - `OTHER_INDIVIDUAL` - A third-party individual. Requires manual approval, except for AUD withdrawal banks, which are active immediately. - `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. AUD withdrawal banks always require the payee's full address (`addressLine1`, `city`, `postalCode`, `country`). **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 - AUD: the BSB must be 6 digits and the account number 1–9 digits; the account is checked with the payout provider before it is saved, and nothing is saved if that fails (502, safe to retry) - For CUSTOMER: beneficiary name is checked against the customer's verified identity (individual) or organization name (corporate), except for AUD withdrawal banks

