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

# Sending payments

> Preview, create and confirm one outbound payment to a wallet, an account, a handle or a bank.

Money leaves an account through one resource: a `Payment` with `intent: send`. The destination shape changes; the call does not, and one `payment_` id carries it through funding, execution and reconciliation.

## Set a payment source first

The sending account needs an active payment source: a connected wallet, or a Vault. Recorded activity is not a payment source, and a deposit balance cannot fund a send.

```bash theme={null}
curl -X PUT https://staging-api-v2.stableyard.fi/v2/accounts/acct_sender/payment-source \
  -u "$APP_ID:$APP_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "type": "connected_wallet", "connectedWalletId": "wallet_123", "assetSymbol": "USDC" }'
```

Payment source controls where outgoing money spends from; settlement preference controls where incoming money lands. Setting either through its own endpoint never changes the other. One path does: linking a wallet that becomes the preferred settlement wallet, on account creation or with `POST /v2/accounts/{accountId}/wallets`, also makes it the payment source, replacing a Vault source.

## Address any destination type

| `destination.type` | Pays | Request fields | Returned on the payment |
| - | - | - | - |
| `upa` | Stablecoin at that account's settlement destination | `account`: `{ "accountId": … }` or `{ "externalUserId": … }` | `accountId` |
| `payment_handle` | Stablecoin at the handle holder's settlement destination | `paymentHandle`, for example `alice` or `alice@partner` | `paymentHandle` and the `accountId` it resolved to |
| `crypto_wallet` | Stablecoin at that address | `chainId`, `address`, `assetCode` | `chainId`, `address`, `assetCode`, `tokenAddress` |
| `external_bank` | Local currency for a one-time beneficiary | `country`, `bankCode`, `accountNumber`, plus `beneficiaryName` | `country`, `currency`, and a `beneficiary` with holder name, bank name, bank code and last four digits |
| `bank_account` | Local currency for a saved, verified beneficiary, including the sender’s own bank | `bankAccountId` | `bankAccountId`, `country`, `currency`, `bankName`, `accountNumberLast4`, `rail` |
| `external_qr` | Local currency in a local merchant's account | `country`, `qrPayload` | `country`, `currency`, `dynamic`, and the `merchant` name and city when available |

The destination is resolved and frozen at creation. For `upa` and `payment_handle` you do not choose the asset: the recipient's settlement profile does, and the amount you name is denominated in it. A recipient with no active settlement destination is refused at creation rather than left pending.

`beneficiaryName` is required for a Philippines bank beneficiary and optional for Vietnam. The raw account number is write-only: it is never returned, and the resolved destination carries only the last four digits. The three crypto destinations work on every supported chain and asset; the fiat destinations are enabled per market and per app. [View supported regions and currencies →](/supported-regions-and-currencies)

## Preview before you commit

`POST /v2/payments/preview` resolves the sender, the destination, the source, the fee and the route without creating anything. It is read-only, takes no `Idempotency-Key`, and can be repeated freely.

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/payments/preview \
  -u "$APP_ID:$APP_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "intent": "send",
    "sender": { "accountId": "acct_sender" },
    "destination": { "type": "crypto_wallet", "chainId": 8453, "address": "0x2222222222222222222222222222222222222222", "assetCode": "USDC" },
    "paymentAmount": { "amount": "10.00", "assetType": "crypto", "assetCode": "USDC", "chainId": 8453 }
  }'
```

| Field | Read it for |
| - | - |
| `sourceAmount` | The total debit to the sender, fee included. `null` when the route cannot be quoted |
| `estimatedFees.totalFeeBps` | The commercial rate applied, with `totalFeeAmountAtomic` in source units |
| `fundingOptions[]` | Per candidate source: `available`, `executionMode`, `unavailableReason` |
| `nextAction.type` | `transaction` or `managed_authorization`, the proof the create call will ask for |

**Stop when the funding option matching your source reports `available: false`.** Show the customer `unavailableReason` instead of creating a payment that cannot execute.

Connected-wallet sends require a direct transfer. A Vault source can also fund a routed send: when `route.required` is `true`, look for the funding option with `executionMode: "vault_routing"`. Confirm the managed authorisation it returns on the same payment. Stableyard then funds the routing deposit address and verifies final delivery itself.

Preview covers the three crypto destinations. `external_bank`, `bank_account` and `external_qr` resolve their funding terms at Payment creation. The US linked-bank route fixes the crypto input without locking the final USD delivery.

## Create the payment

Send the same body with an `Idempotency-Key` derived from your own operation, so a retry reuses it. See [Idempotency](/idempotency). The body is validated strictly: an unknown key is rejected, not ignored.

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/payments \
  -u "$APP_ID:$APP_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout:invoice_8891" \
  -d '{
    "intent": "send",
    "sender": { "accountId": "acct_sender" },
    "destination": { "type": "crypto_wallet", "chainId": 8453, "address": "0x2222222222222222222222222222222222222222", "assetCode": "USDC" },
    "amountMode": "deliver_exact",
    "paymentAmount": { "amount": "10.00", "assetType": "crypto", "assetCode": "USDC", "chainId": 8453 },
    "externalReference": "invoice_8891"
  }'
```

`deliver_exact` fixes what the recipient gets and derives the source debit from it. `collect_exact` fixes what is collected. The amount mode has to match the route.

A one-time external bank beneficiary takes `deliver_exact` with a fiat amount: the amount is what the beneficiary receives.

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/payments \
  -u "$APP_ID:$APP_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout:supplier_4471" \
  -d '{
    "intent": "send",
    "sender": { "accountId": "acct_sender" },
    "destination": { "type": "external_bank", "country": "VN", "bankCode": "970436", "accountNumber": "0123456789" },
    "amountMode": "deliver_exact",
    "paymentAmount": { "amount": "100000", "assetType": "fiat", "assetCode": "VND" },
    "externalReference": "supplier_4471"
  }'
```

A payout to a saved US bank beneficiary takes `collect_exact` and a crypto amount on the sender's payment source. The route fixes the crypto input; the provider confirms the delivered USD at settlement. The beneficiary can be a supplier, contractor or the sender's own bank. See the [US linked-bank walkthrough](/payments/off-ramps#us-linked-bank-payout).

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/payments \
  -u "$APP_ID:$APP_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: withdrawal:withdrawal_485" \
  -d '{
    "intent": "send",
    "sender": { "accountId": "acct_sender" },
    "destination": { "type": "bank_account", "bankAccountId": "bank_account_123" },
    "amountMode": "collect_exact",
    "paymentAmount": { "amount": "100.00", "assetType": "crypto", "assetCode": "USDC" },
    "externalReference": "withdrawal_485"
  }'
```

This collects exactly 100 USDC; Stableyard deducts its fees and forwards the net. **The final USD amount and the conversion cost are known only when the provider settles**, so do not promise the customer a dollar figure: the frozen funding allocations do not lock the provider's rate. The payment succeeds only after Stableyard validates both the exact crypto input and the provider's actual fiat delivery. A route that offers a locked fiat quote takes `deliver_exact` with a fiat amount instead.

Nothing is rounded silently. USDC funding keeps its six-decimal precision, provider conversion fees can include fractions of a cent, and those exact costs are recorded separately from the bank payout. An exact-input USD payout receipt can itself carry up to six decimal places, so read the precision the response supplies rather than assuming cents. The bank must be linked under the sending account and active, with an approved banking relationship in the same app and environment.

## Fund a fiat send before its quote expires

A send to `external_bank`, `bank_account` or `external_qr` returns a `funding` object describing the collection terms, including any available quote. It is null for receive payments and direct sends. Creating the payment does not debit funds or submit the payout.

| `funding` field | What it holds |
| - | - |
| `required` | The exact source amount the sender must deposit |
| `totalFees` | The provider, platform and partner fees, already inside `required`. Do not add it again |
| `exchangeRate` | The available quote’s `rate`, `baseAssetCode` and `quoteAssetCode`, frozen and directional. The US linked-bank route does not lock a USD rate |
| `quoteExpiresAt` | When the frozen quote lapses |
| `depositAddress` | The exact deposit to make. Null until the collection escrow is live |

Each amount is a full object with `amount`, `amountAtomic`, `assetType`, `assetCode`, `decimals`, `chainId` and `tokenAddress`, denominated in the asset the sender funds with. The fee components are broken down on [Assessing fees](/payments/assessing-fees#fiat-sends-carry-a-third-component).

* **Fund inside the window, with room to spare.** A quote that no longer leaves enough time to verify funding and submit the payout is refused with `payment_provider_unavailable`, and `details.minimumQuoteWindowSeconds` says how much time was needed. That refusal is retryable.
* **An expired payout quote cannot be re-priced on the same Payment.** On `payment_quote_expired`, read the original Payment and reconcile any funds already sent. Once the original is terminal and those funds are accounted for, a new attempt needs a new Payment and idempotency key, with a fresh quote and possibly a different price. Never replace a processing or uncertain payout.

A crypto send's action window is the earlier of the quote expiry and ten minutes. The payment's own funding window is `expiresInSeconds`, from 60 to 86,400, defaulting to 600.

## Complete the returned action

Creation persists the Payment and freezes its destination, fees and capability snapshot. It returns the action you owe; it does not prove that funds have been collected or delivered.

<Steps>
  <Step title="Submit the exact proof nextAction asks for">
    Submit the proof `nextAction.type` names, against `nextAction.id`. Never infer the proof from the payment source.

    ```bash theme={null}
    curl -X POST https://staging-api-v2.stableyard.fi/v2/payments/payment_123/confirm \
      -u "$APP_ID:$APP_SECRET" \
      -H "Content-Type: application/json" \
      -d '{ "actionId": "action_123", "proof": { "type": "transaction", "transactionHash": "0x..." } }'
    ```

    `transaction` carries a hash from the wallet that signed the transfer. `managed_authorization` carries `"authorization": "approved"` for a Vault-funded send, with no per-send wallet signature. A proof that does not match the pending action, the source, the amount and the destination is rejected rather than queued.

    `/confirm` is only for outgoing execution. A `transaction` action that carries `depositInstructions` is asking you to fund the payment's escrow instead: do that through checkout and the selected option's `confirmationMode`, not `/confirm`. Calling it on a payment with no outgoing execution returns `409 conflict` with `details.reasonCode: "payment_confirmation_not_supported"`.
  </Step>

  <Step title="A successful confirm is not settlement">
    It means the proof was accepted. Money is still moving.
  </Step>

  <Step title="Read the payment, not the event">
    `GET /v2/payments/{paymentId}`. A [webhook](/webhooks) tells you something changed; the resource tells you what is true. Events can arrive out of order or more than once.
  </Step>

  <Step title="Check both state axes">
    `status` is the financial result and `operationalState` is health. A payment can be `processing` and `requires_intervention` at once, and `status` alone will not say so. See [Status codes](/status-codes).
  </Step>
</Steps>

A send moves through the `stage` values `destination_verifying`, `payout_pending`, `payout_processing`, `payout_confirming` and the settlement stages. On a fiat rail, `offrampStatus` reports the external payout separately, and `failed` there means a verified terminal payout failure after confirmed funding. Both are described on [Reconciliation](/payments/reconciliation#know-what-sits-beside-status).

## Never replace a stuck payment

<Warning>
  **Do not create a second payment for the same obligation.** A send cannot be cancelled: cancellation is available to the receiving participant of a receive payment, and an outgoing payment that already has an execution is refused. Once created, a send completes, expires unfunded, or fails.
</Warning>

A replacement is how one obligation becomes two payouts. The original keeps existing after you stop watching it, and late funds are never reassigned to a newer payment.

| What you saw | Do this instead |
| - | - |
| A timeout, no response | Retry the identical body with the same `Idempotency-Key`, then read the payment |
| `request_in_progress` (409) | Wait, read the payment, then decide |
| `processing` for longer than you expected | Read the payment. Check `operationalState` before assuming failure |
| `operationalState: requires_intervention` | Stableyard operations must act. A new payment does not resolve it |
| A genuinely terminal `failed` or `expired` | Reconcile any funds already sent, then use a new payment and key for a new attempt |

Full rules are on [Errors](/errors).

<Card title="Next: Stablecoin transfers" icon="coins" href="/payments/stablecoin-transfers">
  Send stablecoin to a wallet, or to another account by handle.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.