Skip to content

Send withdrawals

A withdrawal sends funds from your workspace available balance to an external address. It is the half of the product where the security controls live, so this guide covers the wall you will meet before the first request, not only the request itself.

Withdrawals are a Live-side flow: sandbox keys are deliberately not granted withdrawal scopes (the why is in Going live), and the allowlist below applies on mainnet.

Before the first withdrawal: the allowlist

Section titled “Before the first withdrawal: the allowlist”

A live workspace starts with the withdrawal allowlist ON. A withdrawal to an address that is not an approved beneficiary is refused with a 422, whatever else is correct about the request.

Beneficiaries are managed on the dashboard’s Security page, not through this API, because adding a payout destination is a protected action: it requires a strong second factor, and the new address only becomes usable after a cooling-off delay (24 h by default). Your servers and your owners are told when it happens: BENEFICIARY_ADDED and BENEFICIARY_REMOVED webhooks fire, and any change that weakens the policy fires WITHDRAWAL_POLICY_CHANGED with data.weakened: true.

Plan it: add the beneficiaries you will pay before you need them, so the cooling-off has elapsed when the first real withdrawal is due.

POST /v2/fees/quote (scope fees:read) prices a withdrawal with the exact engine that will settle it: send direction, chain, asset and amount, and read back the fee and the net. Use mode: "gross_up" to answer the inverse question, what to withdraw so the recipient nets a target amount. Quotes are indicative; the binding fee is computed at settlement.

POST /v2/withdrawals (scope withdrawals:write). Always send an Idempotency-Key header; the consistency section below is built on it.

Request:

{
"chain": "TRON",
"asset": "USDT_TRON",
"amount": "50",
"destination_address": "TExternalRecipientAddress123456789"
}

Response (201):

{
"id": "e5f6a7b8-0000-0000-0000-000000000005",
"chain": "TRON_MAINNET",
"asset": "USDT_TRON",
"amount": "50.000000",
"fee": "0.900000",
"net_amount": "49.100000",
"status": "PENDING",
"idempotency_key": "3e1f8a20-6a5c-4d5e-9b2a-8c7d6e5f4a3b",
"requested_at": "2026-08-09T11:00:00.000Z"
}

The requested amount is locked immediately: it leaves available for locked the moment the request is accepted, so it cannot be double-spent while the withdrawal is in flight. fee and net_amount are estimates at this point; the final figures come with WITHDRAWAL_CONFIRMED.

Withdrawal path: request and lock, optional holds, broadcast, then confirmed or failed with funds restored

Track it: every status, and which webhook tells you

Section titled “Track it: every status, and which webhook tells you”
Status Meaning Webhook Final?
PENDING Accepted, funds locked, queued for broadcast (the creation response itself) no
PENDING_APPROVAL Above the approval threshold, awaiting a second member WITHDRAWAL_PENDING_APPROVAL no
UNDER_REVIEW Destination held for compliance review WITHDRAWAL_HELD no
PROCESSING Signed and broadcast; txid is now set WITHDRAWAL_PROCESSING no
CONFIRMING Accumulating on-chain confirmations (none) no
CONFIRMED Confirmed on-chain; fee final, breakdown in the payload WITHDRAWAL_CONFIRMED yes
FAILED On-chain failure; the locked funds return to available WITHDRAWAL_FAILED yes
CANCELLED Cancelled before broadcast; funds return to available WITHDRAWAL_CANCELLED yes

Treat CONFIRMED, FAILED and CANCELLED as final: they will not change again. Everything else is transitional, and the terminal outcome always arrives as one of those three.

Two of the transitional states involve humans, and your integration should expect both:

  • PENDING_APPROVAL (double approval). If your workspace policy sets an approval threshold, a withdrawal above it waits for a second member’s approval in the dashboard; the member who created it can never be its approver. Approved, it proceeds to PENDING and the normal flow. Rejected, it ends CANCELLED with the funds restored and WITHDRAWAL_CANCELLED fires. Left past the approval window, it expires and the locked funds return to available; poll the withdrawal if you need to detect that case promptly.
  • UNDER_REVIEW (compliance hold). A destination matching a screening list holds the withdrawal for review instead of signing it. Released, it proceeds; rejected, it ends CANCELLED with the funds restored. The WITHDRAWAL_HELD payload carries the status and the standard fields, nothing about the screening itself.

DELETE /v2/withdrawals/{id} cancels a withdrawal that has not been broadcast (PENDING or PENDING_APPROVAL). The locked funds return to available and WITHDRAWAL_CANCELLED fires. Once PROCESSING, the transaction is on the network and can no longer be cancelled; wait for the terminal state.

Every refusal carries a code; branch on it, never on the message. The ones this endpoint returns, with the action to take, are grouped in the error codes catalogue; the frequent ones are insufficient balance, a malformed destination for the chain, a destination that is a CowriePay-managed address (withdraw to external addresses only), a chain not yet enabled, a compliance freeze on the workspace, and the mainnet allowlist and limit refusals described above.

The failure that costs money is the ambiguous one: you sent the request, the connection died, and you do not know whether the withdrawal exists. The Idempotency-Key header exists for exactly this; send it on every request and the ambiguity disappears:

key = stored_key_for(payout) # generate once per intent, persist it
response = POST /v2/withdrawals (Idempotency-Key: key)
if timeout or network error:
retry POST with the SAME key
-> 201: it did not exist; created now
-> 200 + Idempotent-Replay: true: it existed; this is the original
-> 409: still in flight; wait, retry the SAME key
-> 422 IDEMPOTENCY_MISMATCH: your retry body differs; bug on your side, do not force it

Never generate a fresh key on retry: that is how one payout becomes two. If a request went out with no key and no response, reconcile before retrying: list recent withdrawals (GET /v2/withdrawals) and look for your amount and destination before creating anything.

  • Webhooks: signature verification and the deduplication contract your status handling relies on.
  • Collect deposits: the other half of the money flow.
  • Going live: the full first-day checklist, allowlist included.