5. Off-ramp: send a payout
Bank payout
POST /v1/payments/withdrawals
curl -X POST https://api-staging.checker.finance/v1/payments/withdrawals \
-H "X-Api-Key: $API_KEY" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Nonce: $(uuidgen)" \
-H "X-Signature: $SIGNATURE" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"account_id": "acc_01ARZ3NDEKTSV4RRFFQ69G5USD",
"beneficiary_id": "ben_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"amount": "100.00",
"rail": "FEDWIRE",
"description": "Invoice 4021"
}'Response — 202 Accepted:
{
"withdrawal_id": "wdr_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"account_id": "acc_01ARZ3NDEKTSV4RRFFQ69G5USD",
"beneficiary_id": "ben_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"amount": "100.00",
"rail": "FEDWIRE",
"status": "PROCESSING"
}Use ACH or FEDWIRE for US domestic payouts and SWIFT for a beneficiary outside the US. description is required on every bank rail and is used as the payment reference.
Some SWIFT destination countries (for example Hong Kong, China, India and Kenya) require a purpose_code: PAYMENT_FOR_PHYSICAL_GOODS, PAYMENT_FOR_SERVICES, SALARY_PAYMENT, FAMILY_SUPPORT or OTHER. Not every code is accepted in every country. A payout to such a country without a purpose_code, or with one that country does not accept, returns 202 with status: FAILED and reason_code: INVALID_PURPOSE_CODE before any funds move. Countries that do not require a code ignore it.
The beneficiary is sent exactly amount. The payout fee, reported as fee_amount, is debited from the account in addition to it, and total_amount reports the full debit: the example above sends 100.00 USD and, with a 2.50 fee, debits total_amount 102.50. Both fields appear once the fee is known. Correspondent banks may still deduct their own charges on SWIFT.
On-chain payout
Pay a wallet beneficiary from the settled stablecoin balance. Use the customer's account for the asset being sent as account_id (for a USDT beneficiary, the USDT account — not the USD account the conversion was funded from).
curl -X POST https://api-staging.checker.finance/v1/payments/withdrawals \
-H "X-Api-Key: $API_KEY" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Nonce: $(uuidgen)" \
-H "X-Signature: $SIGNATURE" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"account_id": "acc_01ARZ3NDEKTSV4RRFFQ69G5USDT",
"beneficiary_id": "ben_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"amount": "95.000000",
"rail": "CRYPTO",
"description": "On-ramp settlement"
}'Notes:
CRYPTOis a same-currency on-chain send: the beneficiary's asset must match the account's currency, andamountand the spendable balance are both in that asset. A beneficiary in a different asset returns400 VALIDATION_ERROR;CRYPTOis not offered on the USD account (400 RAIL_NOT_AVAILABLE).quote_idis not allowed onCRYPTOor on bank rails (400 VALIDATION_ERROR); the conversion, if any, happened earlier.descriptionis optional onCRYPTOand becomes the reference on the transfer.- The beneficiary wallet receives exactly
amount; the payout fee (fee_amount) is debited in addition to it, andtotal_amountreports the full debit (amount+fee_amount). To pay out a converted balance, send less than the full balance so the fee fits, as in the example above. - Rails outside the account's entitlements are rejected with
400 RAIL_NOT_AVAILABLE; an amount above the spendable balance returns400 INSUFFICIENT_FUNDS.
Insufficient funds: top up and retry
Before a bank or on-chain payout is submitted, the payout fee is estimated and the request is checked against amount + fee. If the balance cannot cover both, the request is rejected with 400 INSUFFICIENT_FUNDS before any funds move, and no withdrawal is created. details gives the shortfall:
{
"error_code": "INSUFFICIENT_FUNDS",
"message": "requested amount plus fee exceeds available balance",
"details": {
"fee_amount": "1.55",
"fee_currency": "USD",
"required_amount": "6.55",
"available_amount": "5.00"
}
}Top up the account by at least required_amount − available_amount (or lower amount), then retry. The rejected request releases its Idempotency-Key, so the same key can be reused. fee_amount is an estimate; the accepted payout reports the actual fee. If the balance drops between this check and submission, the payout is instead returned with status: FAILED and reason_code: INSUFFICIENT_FUNDS; top up and submit a new payout.
Check the payout status
GET /v1/payments/withdrawals/{withdrawal_id}
curl https://api-staging.checker.finance/v1/payments/withdrawals/wdr_01ARZ3NDEKTSV4RRFFQ69G5FAV \
-H "X-Api-Key: $API_KEY" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Nonce: $(uuidgen)" \
-H "X-Signature: $SIGNATURE"Response — 200 OK:
{
"withdrawal_id": "wdr_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"beneficiary_id": "ben_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"amount": "100.00",
"rail": "FEDWIRE",
"status": "COMPLETED",
"fee_amount": "2.50",
"total_amount": "102.50",
"description": "Invoice 4021"
}Poll until the withdrawal reaches COMPLETED, FAILED, or CANCELLED, then confirm the balance with GET /v1/payments/accounts/{account_id}/balance. For this payout the balance drops by total_amount (102.50 = amount + fee_amount), and the beneficiary receives 100.00. While the payout is PROCESSING, available already excludes total_amount and total still includes it; total drops once the payout settles. On a quote-driven payout (CHINA_OFFRAMP) total_amount equals amount, because the fee is priced into the quote.
Updated 3 days ago
