Skip to main content
A withdrawal sends tokens from an account’s wallet to any address you name. Reap covers the network fee and charges a flat fee per chain, so you do not have to hold gas on any chain or manage a signing pipeline of your own.
Withdrawals apply to User-Funded projects, where each account holds its funds in its own wallets. In Program-Funded projects the user’s funds sit on your side, so you record a withdrawal by posting against the account instead.

How a withdrawal works

Every withdrawal needs a signature from the wallet owner. Reap holds one key and you or your user holds the other. Nothing moves on chain without both, so neither side can withdraw alone. The key is the signer you registered when you created the account. Each chain family has its own curve, so an account on both EVM chains and Solana carries two signer keys and each one signs only its own withdrawals.
1

Check what can be withdrawn

Call the assets endpoint. Each chain reports a withdrawable cap and the withdrawalFee for that chain.
2

Initiate the withdrawal

Initiate crypto withdrawal with the account, chain, asset, gross amount, and destination address. Reap reserves the funds and returns the withdrawal in PENDING_SIGNATURE. An idempotency key is required.
3

Fetch a signing payload

Mint signing payload returns payloadToSign, the executionId that identifies it, and validUntil.
4

Sign it

The wallet owner signs payloadToSign with their registered key.
5

Submit the signature

Submit withdrawal signature with the signature and the executionId you were given. The withdrawal moves to PROCESSING and Reap broadcasts the transfer.
6

Wait for completion

transactionId appears once the transfer reaches the network. The withdrawal reaches COMPLETED when the transfer is irreversible on chain.

Signing

You do not need to parse payloadToSign. Forward it to the wallet exactly as it arrives and sign that string. Do not re-encode, hash, or trim it. EVM chains
Solana

Who holds the signing key

You choose whether your backend signs or the end user does. The API, the payload, and the code above are the same either way, so this is a product decision rather than an integration one. Reap holds the other key in both cases.
You register a key you control and sign every withdrawal server-side. Your own rules decide what leaves an account. That is what you want when withdrawals run on a schedule, clear an internal approval step, or have to work while the user is offline. The user never sees a prompt, so a withdrawal finishes in one call from your product.The tradeoff is concentration. One key authorizes every account, so whoever holds it can withdraw from all of them. Keep it in a KMS or HSM and treat access to it as access to client funds.
The key lives in the user’s wallet and you relay payloadToSign to their device. Nothing leaves an account without the person. A breach of your systems cannot move funds on its own, and the user holds a real veto over each withdrawal. This is the closer fit when you present the wallet as the user’s own.The tradeoff is that the user has to be there. Scheduled and automated withdrawals stop being possible. Every prompt costs you some drop-off, and you own the recovery story for a user who loses their key.

Payloads are short-lived

A payload commits chain resources that other traffic can take, so validUntil is a few minutes out. Fetch a payload immediately before you sign it rather than at the start of a session. Each fetch retires the previous payload. Only the newest one can be submitted, which is why you pass back the executionId you were handed: a signature over a retired payload is rejected and you fetch a fresh one. Fetching a payload never affects the withdrawal itself, so you can do it as often as you need inside the signature window.

Verify before you sign

payloadToSign is readable JSON, not a hash. Your backend can decode it and check the destination, asset, and amount match the withdrawal you requested before relaying it to a user. On EVM the transfer sits in the typed data under parameters.payload; on Solana it is a hex-serialized transaction under parameters.unsignedTransaction, which decodes with @solana/web3.js. The signature covers those exact bytes. Change one byte and it no longer verifies, so what you inspect is what executes.

Fees and amounts

The amount you request is the gross. The fee comes out of it rather than on top, so netAmount is what the destination receives and the gross is what leaves the account. The fee is flat because the cost behind it is flat. Sending a transfer costs the same whatever it carries, so charging a percentage would bill a large withdrawal for work nobody did. Solana costs more despite its cheap network fees. A first transfer to a destination has to open a token account for it, and that account needs a rent deposit in SOL that Reap funds and does not get back. Each withdrawal reports the fee it was charged in fee.amount, fixed when you initiated it. The assets endpoint reports the current fee per chain in the asset you hold, so you can show a user what a withdrawal will cost before they commit to one.

The withdrawable cap

withdrawable on the assets endpoint is the largest gross amount you can request on that chain. It nets out outstanding card spend, including authorizations still on hold. A withdrawal therefore cannot take the available balance below zero. It reads zero while the account is not ACTIVE. Two things to keep in mind:
  • It is a per-chain figure that assumes nothing else is withdrawn, so the caps across chains cannot all be taken at once.
  • It is advisory and recomputed on every request. The authoritative check happens when you initiate, and a card authorization in between can move it.

Statuses

You receive a CRYPTO_WITHDRAWAL_CREATED webhook when the withdrawal is created, and a CRYPTO_WITHDRAWAL_STATUS_UPDATED webhook at every status change after that. You can also read a withdrawal at any time with Get crypto withdrawal, and withdrawals appear in the activity feed alongside deposits and card transactions. Reserved funds stop backing card spend for as long as the withdrawal is open. They return to the balance on EXPIRED, CANCELLED, and FAILED.
PROCESSING with a transactionId means the transfer is on the network, not that it has landed. Treat only COMPLETED as final.

Failures

failureReason says what went wrong:
  • TRANSACTION_FAILED: the transfer did not go through on chain. The funds never left the account.
  • ACCOUNT_RESTRICTED: Reap restricted the account while the withdrawal was open. See account status.
  • REJECTED_BY_COMPLIANCE: the destination did not clear screening. Reap pauses withdrawals on the account and reviews it. Further withdrawals are refused until that review finishes.

The signature window

A withdrawal stays signable for 15 minutes from the moment you initiate it, reported as expiresAt. After that it moves to EXPIRED and Reap releases the funds. The window is short because an unsigned withdrawal costs the account something for nothing. The reserved funds no longer back card spend, and one open withdrawal per wallet and chain blocks the next. Signing normally follows initiation within the same session. If a user walks away, let the withdrawal expire or cancel it. Initiating another one costs nothing. Cancelling works only while the withdrawal is awaiting a signature. Once it is signed and broadcasting there is nothing to cancel.
Withdrawals are irreversible. Reap cannot recover funds sent to a wrong address, and the destination must be a valid address on the chain you named. Check the address with your user before you initiate.

Testing in sandbox

The flow works end to end on testnets with the test tokens listed under Supported Assets. Fund an account, then withdraw to any address you control on the same chain and watch for the status webhooks. Reap pays the testnet gas, the same as in production.