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 parsepayloadToSign. Forward it to the wallet exactly as it arrives and sign that string. Do not re-encode, hash, or trim it.
EVM chains
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.Your backend signs
Your backend signs
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 end user signs
The end user signs
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, sovalidUntil 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, sonetAmount 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.
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 asexpiresAt. 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.