> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reap.global/llms.txt
> Use this file to discover all available pages before exploring further.

# Withdrawing Funds

> How to move tokens out of an account wallet to an external address.

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.

<Note>
  Withdrawals apply to [User-Funded](/program-configuration/funding-models#user-funded) projects, where each account holds its funds in its own wallets. In [Program-Funded](/program-configuration/funding-models#program-funded) projects the user's funds sit on your side, so you record a withdrawal by [posting](/virtual-assets/postings) against the account instead.
</Note>

## 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](/accounts/overview#the-signer-key) 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.

<Steps>
  <Step title="Check what can be withdrawn">
    Call the [assets endpoint](/api-reference/accounts/get-account-assets). Each chain reports a `withdrawable` cap and the `withdrawalFee` for that chain.
  </Step>

  <Step title="Initiate the withdrawal">
    [Initiate crypto withdrawal](/api-reference/crypto-withdrawals/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](/api-reference/idempotency) is required.
  </Step>

  <Step title="Fetch a signing payload">
    [Mint signing payload](/api-reference/crypto-withdrawals/mint-signing-payload) returns `payloadToSign`, the `executionId` that identifies it, and `validUntil`.
  </Step>

  <Step title="Sign it">
    The wallet owner signs `payloadToSign` with their registered key.
  </Step>

  <Step title="Submit the signature">
    [Submit withdrawal signature](/api-reference/crypto-withdrawals/submit-withdrawal-signature) with the signature and the `executionId` you were given. The withdrawal moves to `PROCESSING` and Reap broadcasts the transfer.
  </Step>

  <Step title="Wait for completion">
    `transactionId` appears once the transfer reaches the network. The withdrawal reaches `COMPLETED` when the transfer is irreversible on chain.
  </Step>
</Steps>

```mermaid theme={null}
sequenceDiagram
    participant You as Your backend
    participant Reap
    participant Owner as Wallet owner
    You->>Reap: Initiate withdrawal
    Reap-->>You: PENDING_SIGNATURE, funds reserved
    You->>Reap: Fetch signing payload
    Reap-->>You: payloadToSign, validUntil
    You->>Owner: Relay payload
    Owner-->>You: Signature
    You->>Reap: Submit signature
    Reap-->>You: PROCESSING
    Reap-->>You: COMPLETED
```

## 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**

```typescript theme={null}
const signature = await walletClient.signMessage({ account, message: payloadToSign });
```

**Solana**

```typescript theme={null}
const signature = bs58.encode(
  nacl.sign.detached(Buffer.from(payloadToSign), keypair.secretKey)
);
```

### 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.

<AccordionGroup>
  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

### 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.

| Chain   | Fee      | Minimum withdrawal |
| ------- | -------- | ------------------ |
| Base    | 0.30 USD | 5 USD              |
| Polygon | 0.30 USD | 5 USD              |
| Solana  | 0.75 USD | 5 USD              |

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](/api-reference/accounts/get-account-assets) 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](/accounts/overview#how-balance-is-computed) 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

| Status              | What it means                                                  |
| ------------------- | -------------------------------------------------------------- |
| `PENDING_SIGNATURE` | Funds are reserved and Reap is waiting for the owner signature |
| `PROCESSING`        | Signed and executing on chain                                  |
| `COMPLETED`         | The transfer is irreversible on chain                          |
| `EXPIRED`           | The signature window closed and Reap released the funds        |
| `CANCELLED`         | You cancelled it and Reap released the funds                   |
| `FAILED`            | The withdrawal did not go through, see `failureReason`         |

You receive a [CRYPTO\_WITHDRAWAL\_CREATED](/api-reference/crypto-withdrawal-created) webhook when the withdrawal is created, and a [CRYPTO\_WITHDRAWAL\_STATUS\_UPDATED](/api-reference/crypto-withdrawal-status-updated) webhook at every status change after that. You can also read a withdrawal at any time with [Get crypto withdrawal](/api-reference/crypto-withdrawals/get-crypto-withdrawal), and withdrawals appear in the [activity feed](/api-reference/activities/list-activities) 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`.

<Warning>
  `PROCESSING` with a `transactionId` means the transfer is on the network, not that it has landed. Treat only `COMPLETED` as final.
</Warning>

### 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](/accounts/overview#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](/api-reference/crypto-withdrawals/cancel-crypto-withdrawal) 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.

<Warning>
  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.
</Warning>

## Testing in sandbox

The flow works end to end on testnets with the test tokens listed under [Supported Assets](/accounts/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.
