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.

This endpoint requires an API key. Browser-session withdrawals must use the dashboard approval flow.

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)

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 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. Reusing a key for a different customer returns 409.

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

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