---
url: /docs/fiat/payouts.md
description: >-
  Execute a payout against a payout quote and track it from processing to
  completed, failed, or refunded.
---

A payout executes the transfer locked in by a [payout quote](/fiat/payout-quotes): stablecoins move out of the funding source and fiat lands in the recipient's [bank account](/fiat/bank-accounts). As a bank-rails integrator, you call one endpoint and then track status; the settlement leg is plumbing.

## How it works

Every payout needs a funding source, the wallet the settlement stablecoins are pulled from:

* **BlindPay-managed wallet balance**: the simple path. BlindPay custodies the wallet on the customer's behalf, so there is no client-side signing. Pass its address as `sender_wallet_address` and BlindPay moves the funds directly.
* **External wallet**: if the funds live in a customer-controlled wallet instead, you must authorize the transfer on-chain before calling the payout endpoint (an ERC-20 `approve` on EVM, an authorize-and-sign step on Stellar, or a delegation on Solana). See [Stablecoin payouts](/stablecoin/payouts) for the full mechanics; do not build the on-chain step from this page.

## Prerequisites

You also need a [customer](/fiat/) who has completed KYC, an [approved bank account](/fiat/bank-accounts), and an unexpired [payout quote](/fiat/payout-quotes).

## Execute a payout

Check the required fields in the [BlindPay API Docs](https://api.blindpay.com/reference#tag/payouts/POST/v1/instances/{instance_id}/payouts/evm){target="\_blank"}.

```bash [cURL]
curl --request POST \
  --url https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "quote_id": "qu_000000000000",
  "sender_wallet_address": "YOUR_WALLET_ADDRESS"
}'
```

```js [index.js]
const response = await fetch(
  'https://api.blindpay.com/v1/instances/in_000000000000/payouts/evm',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      quote_id: 'qu_000000000000',
      sender_wallet_address: 'YOUR_WALLET_ADDRESS',
    }),
  }
)

const payout = await response.json()
```

`quote_id` can only back one payout. A second call with the same quote returns an error; create a new quote instead.

### Response

```json
{
  "id": "po_000000000000",
  "status": "processing",
  "sender_wallet_address": "0x...",
  "receiver_id": "re_000000000000",
  "bank_account_id": "ba_000000000000",
  "offramp_wallet_id": null,
  "billing_fee_amount": null,
  "partner_fee": 0,
  "tracking_complete": { "step": "processing" },
  "tracking_payment": { "step": "processing" },
  "tracking_transaction": { "step": "processing" },
  "tracking_partner_fee": { "step": "on_hold" },
  "tracking_liquidity": { "step": "processing" }
}
```

| Field | Type | Notes |
| --- | --- | --- |
| `id` | string | The payout ID (`po_...`). |
| `status` | string | See status lifecycle below. |
| `sender_wallet_address` | string | The funding source address the crypto was pulled from. |
| `receiver_id` | string | The customer this payout belongs to (`re_...`). |
| `bank_account_id` | string | The recipient bank account (`ba_...`). |
| `partner_fee` | number | Nonzero only if the quote referenced a `partner_fee_id`. See [partner fees](/learn/partner-fees). |
| `tracking_complete`, `tracking_payment`, `tracking_transaction`, `tracking_partner_fee`, `tracking_liquidity` | object | Sub-status objects with a `step` of `processing`, `on_hold`, `pending_review`, or `completed`. Poll these or rely on webhooks for progress detail beyond the top-level `status`. |

## Status lifecycle

| Status | Meaning |
| --- | --- |
| `processing` | Default on creation. The stablecoins are being pulled from the funding source and the fiat transfer is in flight. |
| `on_hold` | Held for review. All SWIFT payouts start here. ACH, wire, and RTP payouts also pass through `on_hold` as a standard step, for compliance review after the crypto is collected. |
| `completed` | The fiat landed in the recipient's bank account. Terminal. |
| `failed` | The payout did not complete (for example, a review timeout or a rejected compliance check). Terminal. |
| `refunded` | The stablecoins were returned to the funding source instead of being converted to fiat. Terminal. |

## Testing

Development instances complete payouts automatically. Force a `failed` or `refunded` outcome by setting the payout quote's `request_amount` to one of these sentinel values before executing the payout:

| `request_amount` | Result |
| --- | --- |
| `66600` ($666.00) | `failed` |
| `77700` ($777.00) | `refunded` |
| any other amount | `completed` |

## Webhooks

| Event | Fires when |
| --- | --- |
| `payout.new` | A payout is created. |
| `payout.update` | A payout changes status (for example, `processing` to `on_hold`). |
| `payout.complete` | A payout reaches `completed`, `failed`, or `refunded`. |
| `payout.partnerFee` | A payout completes and a partner fee is owed. See [Partner fees](/learn/partner-fees). |

## Related

* [Payout quotes](/fiat/payout-quotes): lock the exchange rate and fee split before executing
* [Bank accounts](/fiat/bank-accounts): add and manage recipient bank accounts
* [Send](/fiat/send): the full three-step payout pattern
* [Stablecoin payouts](/stablecoin/payouts): on-chain authorization mechanics for external wallets, and USDB minting for testing
* [Webhooks](/learn/webhooks): full event catalogue and signature verification
