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.POST /v2/accounts/{accountId}/wallets, also makes it the payment source, replacing a Vault source.
Address any destination type
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 →
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.
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 anIdempotency-Key derived from your own operation, so a retry reuses it. See Idempotency. The body is validated strictly: an unknown key is rejected, not ignored.
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.
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.
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 toexternal_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.
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.
- 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, anddetails.minimumQuoteWindowSecondssays 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.
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.1
Submit the exact proof nextAction asks for
Submit the proof
nextAction.type names, against nextAction.id. Never infer the proof from the payment source.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".2
A successful confirm is not settlement
It means the proof was accepted. Money is still moving.
3
Read the payment, not the event
GET /v2/payments/{paymentId}. A webhook tells you something changed; the resource tells you what is true. Events can arrive out of order or more than once.4
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.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.
Never replace a stuck payment
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.
Full rules are on Errors.
Next: Stablecoin transfers
Send stablecoin to a wallet, or to another account by handle.