Common stablecoin payout mistakes: wrong network, expired quotes, and bad bank details

The mistakes that make stablecoin payouts fail or stall: wrong network or token, expired quotes, deposits below minimums, bad bank details, and cut-offs.

Most stablecoin payouts that fail don't fail on-chain. They fail at the edges: a token sent on a network the provider doesn't support, a quote that expired while someone clicked "confirm", a deposit smaller than the fee, a bank account with one wrong digit, or a US rail that closed for the weekend. Each one is preventable with a check you can write before go-live.

This is the list we'd hand to any team shipping its first payout integration. Every item has shown up in real integrations.

Mistake 1: sending a token on the wrong network

A stablecoin is a token contract on a specific blockchain. "USDC" on Ethereum and "USDC" on Solana are different contracts, and a payout API accepts only the combinations it supports. Two facts catch people most often:

  • USDC doesn't exist on Tron. Circle doesn't issue it there. If your treasury sits in USDT on Tron, settle that way or convert first.
  • USDT isn't on every chain your provider supports. On BlindPay, USDT isn't available on Base, Arbitrum, or Stellar.

EVM chains add a trap. Ethereum, Polygon, Base, and Arbitrum all use the same 0x address format, so a wallet will happily send to the right address on the wrong chain. The funds land at that address on a network the receiver isn't watching. Whether they come back depends on who holds the private key there.

Check the contract, not the ticker. Several chains carry both native USDC and an older bridged version, and a payout API expects the native one. USDC vs USDT for payments covers which token fits which flow, and what happens on-chain in a stablecoin payment shows what the transfer actually does.

Mistake 2: mixing test and production

Development and production are separate worlds, and the token tells you which one you're in. On BlindPay, a development instance uses USDB, a test stablecoin, on testnets. A production instance uses USDC or USDT on mainnets. A mismatched pair, like USDC on Sepolia or USDB on Polygon, is rejected.

Two consequences:

  • Config that works in development can't be pointed at production by swapping the API key alone. The network names and tokens change too.
  • Tron has no testnet, so a Tron flow can't be exercised on a development instance at all. Plan a small, supervised first payout in production.

Sandbox vs production lists the other things a sandbox won't show you.

Mistake 3: letting the quote expire

A payout quote locks the exchange rate, the fees, and the exact amount the recipient gets. It also has a short life. On BlindPay a payout quote lasts 5 minutes by default, and SEPA quotes can be shorter because the rail's own deadline is tighter.

The bugs we see:

  1. Wrong time unit. expires_at is epoch milliseconds. Treat it as seconds and your code thinks the quote expires decades from now.
  2. Human approval in the middle. A quote created, then sent to a finance approver who clicks 20 minutes later. Create the quote after approval, not before.
  3. Reusing a quote. One quote backs one payout. A second payout with the same quote fails. Retry with a new quote, and use an idempotency key so a network retry can't send the payout twice.

Stablecoin API quotes explained covers fee direction and minor units, the other two places quote math goes wrong.

Mistake 4: deposits below the minimum

Some flows have floors that aren't obvious until money is stuck under them.

SituationFloor on BlindPayWhat goes wrong below it
Offramp wallet on Tron200 USDT minimum, 15 USDT fee per depositThe deposit isn't converted into a payout
Offramp wallet on Solana50 USDC minimumThe deposit isn't converted into a payout
Offramp wallet on other supported chainsMust cover the feesA deposit smaller than the fees isn't converted
SWIFT payout100 USD requested amountThe quote is rejected with swift_minimum_is_100_usd

The offramp wallet case is the dangerous one. An offramp wallet is a deposit address that converts every incoming transfer into a bank payout. Nobody calls an API to trigger it, so there's no API call to return an error either. A 20 USDT test deposit on Tron just sits there. Tell whoever pays into the address what the minimum is, and monitor for deposits that don't turn into payouts.

Mistake 5: bad bank details

The stablecoin leg can be perfect and the payout still bounces because the bank side is wrong. The usual suspects:

  • Mexico: a CLABE with a typo. CLABEs are 18 digits with a check digit at the end, so validate it in your form before it ever reaches a payout.
  • Brazil: a Pix key that belongs to a different person, or a phone key saved without the +55 country code.
  • US: an ACH routing number used for RTP when the receiving bank isn't RTP-eligible.
  • Names: a nickname or a company's trade name where the bank expects the legal name.

Validation should happen once, when the account is saved, not at 2 a.m. on payday. BlindPay validates bank account data on creation (format, length, country rules) the same way on development and production instances, so a bad CLABE fails in testing too. How to send USDC to a bank account in Brazil walks through verifying a receiver end to end.

Mistake 6: forgetting that fiat rails keep business hours

The blockchain runs 24/7. Most of the banking world doesn't. A payout funded at 5 p.m. on a Friday can still wait until Monday, because the fiat rail is the slow part.

RailDaily cut-off (ET)Settlement
ACH9:00 PM1 to 3 business days
Same-Day ACH3:00 PMSame business day
Domestic wire3:00 PMSame business day
International SWIFT10:30 AMUp to 5 business days
Pix (Brazil)None, runs 24/7Minutes
SPEI (Mexico)None, runs 24/7Minutes

Requests after a cut-off move to the next business day, and business days exclude weekends and US federal holidays. If your users expect Friday-night payouts, route them over rails that run on weekends, or tell them when the money will actually land. How long a stablecoin payout takes has the full table by country.

Mistake 7: treating every terminal status as "done"

A payout ends in one of three states, and they mean different things for your ledger:

  • Completed. The fiat landed. Mark it paid.
  • Refunded. The stablecoins went back to the wallet that funded the payout instead of being converted. On BlindPay, stablecoin refunds process right away.
  • Failed. The payout didn't complete. It does not refund automatically, and someone has to open a support request.

Code that treats "not completed" as "refunded" will show a balance that doesn't exist. Handle all three explicitly, alert on failed, and reconcile against webhooks instead of polling once and hoping. Fiat refunds on the payin side are slower: they wait for the bank to return the funds, and fees may apply.

Mistake 8: skipping the paperwork on SWIFT payouts

SWIFT payouts carry more compliance than local rails. On BlindPay every SWIFT payout starts on_hold. If the bank account belongs to someone other than your customer, the payout also needs a document proving the relationship, such as an invoice, purchase order, or contract. If it's your customer's own account, the beneficiary name must match the customer, or the request fails.

Teams that don't plan for this see every international payout "stuck" on day one. Collect the invoice when you collect the payment request, and pass the invoice number with the payout. Automating KYC and KYB covers the onboarding side.

What does a pre-launch checklist look like?

MistakeHow to prevent it
Wrong network or tokenHardcode supported pairs, check the token contract, reject others in your UI
Test vs production mix-upKeep network and token config per environment, not just the API key
Expired or reused quoteQuote after approval, parse expires_at as milliseconds, one quote per payout
Deposit below minimumPublish minimums to payers, monitor deposit addresses
Bad bank detailsValidate formats and check digits when the account is saved
Weekend and cut-off delaysShow the real arrival time per rail, prefer 24/7 rails where they exist
Status handlingHandle completed, refunded, and failed separately, reconcile on webhooks
SWIFT documentsCollect invoices up front, match first-party names exactly

Most of these checks cost a few lines of code. Skipping them costs support tickets, and occasionally money. How to choose an on/off ramp provider has the tests to run on the provider side.

How does BlindPay help catch these before they cost money?

BlindPay puts most of the guardrails in the API. Quote creation enforces the supported token and network pairs and returns a descriptive error instead of accepting a transfer it can't settle. Bank account data is validated on creation, on development instances too. Every payout reports its status through webhooks, from processing to completed, failed, or refunded.

The development instance lets you rehearse failure on purpose. Set a payout's request_amount to 66600 ($666.00) to force failed, or 77700 ($777.00) to force refunded, and make sure your ledger does the right thing with each. The full lists are in supported chains, cut-off times, and payouts.

Before you go live, run all three terminal statuses through your code in development, then send one small supervised payout per rail in production. For the full picture of how the pieces connect, start with stablecoin payments explained.

FAQ