Rails-first vs chain-first: choosing between an abstracted and an advanced blockchain payments API
Rails-first or chain-first? How BlindPay's Abstracted and Advanced API flavors differ, what changes in your code, and a go-live checklist for both.
Rails-first or chain-first is a choice about what your code talks to. An abstracted blockchain payments API speaks in bank terms: virtual accounts, payins, and payouts over ACH, wire, SWIFT, Pix, or SPEI, with stablecoins settling underneath. An advanced API exposes wallets, chains, tokens, and on-chain authorization. BlindPay documents one API in both flavors.
If you are still deciding whether you need a blockchain at all, start with blockchain payments vs crypto payments. This page assumes you are building and want to pick an integration shape.
Key takeaways
- Abstracted means bank-rail concepts. Advanced means wallets, chains, and tokens. Same API, same keys.
- Pick by where your users' money lives: in bank accounts, or in wallets they control.
- External wallets authorize each payout on-chain: ERC-20 approve, a signed Stellar XDR, or a Solana delegation.
- Development instances use USDB on testnets and complete payouts automatically.
- Go live with five things in place: webhooks, idempotency, error handling, reconciliation, monitoring.
What are the Abstracted and Advanced flavors?
They are two ways of reading the same BlindPay API. The overview page has both: the Abstracted flavor describes bank rails, and the Advanced flavor describes the stablecoin mechanics.
Definition: An abstracted payments API presents payment-rail objects (bank accounts, deposits, payouts) and handles the blockchain leg internally. An advanced payments API exposes that leg: which wallet holds funds, which chain and token settle the transfer, and how the sender authorizes it.
In the Abstracted flavor, every dollar or real still settles through stablecoins, but BlindPay manages the conversion step. You never sign anything on-chain. In the Advanced flavor, you choose between BlindPay-managed wallets and customer-controlled wallets, pick networks and tokens, and handle authorization per chain.
The docs switch flavor with a query parameter, so /docs/payouts?flavor=abstracted and /docs/payouts?flavor=advanced are the same page through two lenses.
How does Store, Receive, Send look in each flavor?
The same three verbs, described two ways. This table summarizes BlindPay's Store, Receive, and Send pages plus payins and payouts.
| Verb | Abstracted reading | Advanced reading |
|---|---|---|
| Store | A balance BlindPay holds for the customer (managed wallet) | A managed wallet (bl_..., custodial, beta) or an external blockchain wallet (bw_..., customer-controlled) |
| Receive | A bank deposit (ACH, wire, SWIFT, Pix, SPEI, Transfers, PSE) becomes a payin | Stablecoins arrive in a managed wallet, tracked by wallet.inbound; or a payin delivers stablecoins to any registered wallet |
| Send | A payout to a bank account over ACH, wire, RTP, SWIFT (POBO/COBO), Pix, SPEI, ACH COP, Transfers, SEPA, or Faster Payments | Stablecoins move out of a managed wallet to another address, or are pulled from a wallet to fund a fiat payout |
The Abstracted flavor maps to what a finance team already knows. The Advanced flavor maps to what a crypto-native product already does. Neither one is the beginner version.
What changes in your code between the two?
Mostly the funding step of a payout. Everything before it (customer, bank account, quote) and after it (status, webhooks) is the same.
Every payout starts with a payout quote: a bank_account_id, a network and token for the funding leg, currency_type, cover_fees, and request_amount in minor units. The quote locks the rate and fees for 5 minutes. Then the funding source decides the next call:
| Funding source | Authorization | Execute call |
|---|---|---|
| BlindPay-managed wallet | None, BlindPay moves the funds | POST /payouts/evm with the quote ID and the wallet address, on any supported chain |
| External wallet on an EVM chain | ERC-20 approve for the amount in the quote's contract object | POST /payouts/evm with the quote ID and sender address |
| External wallet on Stellar | POST /payouts/stellar/authorize returns an unsigned transaction; you sign the XDR | POST /payouts/stellar |
| External wallet on Solana | POST /prepare-delegate-solana, then sign and submit the delegation | POST /payouts/evm with the quote ID and Solana address |
The three authorization methods match each chain's own token model. EIP-20 defines approve as allowing a spender to withdraw up to a set value. Solana's token program lets an account owner delegate authority over some or all of a balance with the Approve instruction. Stellar encodes transactions in XDR, so the sender signs a payment transaction instead of granting an allowance.
Quotes and approvals are single-use. To send again, quote again and authorize again. The per-chain tutorials are payout with EVM, payout with Stellar, and payout with Solana.
Which chains and tokens can you use?
Nine chains, with token support that varies by chain. The supported chains reference is the source of truth for network and token values; check it rather than the shorter tables inside individual guides.
| Chain | Mainnet stablecoins | Development testnet |
|---|---|---|
| Ethereum | USDC, USDT | Ethereum Sepolia |
| Polygon | USDC, USDT | Polygon Amoy |
| Base | USDC | Base Sepolia |
| Arbitrum | USDC | Arbitrum Sepolia |
| Tempo | USDC, USDT | Tempo Testnet |
| Arc | USDC | Arc Testnet |
| Stellar | USDC | Stellar Testnet |
| Solana | USDC, USDT | Solana Devnet |
| Tron | USDT | none |
Production instances accept only USDC or USDT on mainnets. Development instances accept only USDB on testnets, and a mismatched pair is rejected. Tron has no testnet, so Tron flows can only be exercised in production.
In the Advanced flavor you can also move USDC between managed wallets across Ethereum, Polygon, Base, Arbitrum, and Arc with Circle's CCTP v2, which burns USDC on the source chain and mints it on the destination. Other tokens stay on the same network. If the newer chains are unfamiliar, payment blockchains like Tempo and Arc explains what they are.
Which flavor fits your product?
Decide by who holds the money between steps. Five common shapes:
| Product | Flavor | Why |
|---|---|---|
| Payroll or contractor payouts funded by bank transfer | Abstracted | Users never see a wallet; deposit, hold a balance, pay out |
| A US account for customers abroad (collections) | Abstracted | Virtual accounts and payins are bank objects |
| A crypto wallet app adding cash-out to local banks | Advanced | Users already control wallets; payouts are authorized from them |
| A treasury tool moving USDC between chains | Advanced | Transfers, networks, and CCTP routes are the product |
| A platform doing both | Both | Abstracted screens for finance users, Advanced flows for crypto users |
Custody follows from the choice. BlindPay operates as a non-custodial payment processor for payout flows from customer-controlled wallets: you authorize the exact amount and BlindPay executes that single transaction. If a payout cannot be delivered after the stablecoins are collected, BlindPay refunds them to the quote's refund wallet, or to the sending wallet when none is set; on Pix and other non-USD local rails the refund is automatic, while USD payouts and payouts whose review times out are refunded after an operator review. Optional BlindPay-managed wallets (beta) are custodial when you choose that path. The wallet custody comparison covers the trade-offs.
How do you test both flavors before going live?
On a development instance, which behaves like production without real money. Per the sandbox vs production guide:
- Every customer is auto-approved. Name one
Failto test a rejection. - Wallets use USDB, funded by a payin that auto-completes about 30 seconds after creation.
- Payouts still run the on-chain authorization step, then complete automatically with no real bank transfer.
- A payout quote with
request_amountof 66600 forcesfailed, and 77700 forcesrefunded.
Run each flavor's happy path, then both forced outcomes, and confirm your webhook handler, database, and user-facing screens all agree. What sandbox testing misses lists the gaps to cover in production.
What belongs on the go-live checklist?
Five areas, the same for both flavors. Tick every line before the first production payout.
1. Webhooks
- Register an
httpsendpoint per instance; BlindPay allows up to 25. - Verify
svix-id,svix-timestamp, andsvix-signatureagainst the raw body, and reject timestamps outside a 5-minute window (verification docs). - Deduplicate on
svix-id, which stays the same across redeliveries. - For payouts, handle
payout.new,payout.update, andpayout.complete, and branch onstatus, since a failed payout can arrive as an update.
2. Idempotency
- Send an
Idempotency-Keyheader on everyPOST,PUT,PATCH, andDELETE. - Retry with the exact same body bytes. A different body returns
422 idempotency_key_payload_mismatch. - Respect
409 idempotency_key_in_flightand itsRetry-Afterheader. Keys last 24 hours (idempotency docs).
3. Error handling
- Treat an expired quote as normal: re-quote, show the new numbers, ask again. Never execute a stale quote.
- Map a
400on an unsupported network and token pair to a configuration bug, not a user error. - Expect SWIFT payouts to third parties to start
on_holduntil compliance documents are approved.
4. Reconciliation
- Store amounts in minor units, exactly as the API returns them.
- Keep your own payment ID next to the BlindPay quote, payout, or payin ID, and the on-chain transaction hash from the tracking objects.
- Run a daily job that matches your ledger to payout and payin statuses. The webhooks and reconciliation guide has the matching logic.
5. Monitoring
- Alert on payouts in
on_holdorprocessinglonger than the rail's documented window. - A
failedpayout, or one sitting past review, does not refund on its own. Contact support rather than wait. - Use separate API keys for development and production, restrict production keys to your server IPs with
ip_whitelist, and rotate by deleting and recreating a key (API keys docs).
Then send one small production payout on each rail you plan to use, and watch it all the way to completed.
What to do next
Pick the flavor that matches where your users' money lives, then run its happy path and both forced outcomes on a development instance. If you are still comparing vendors, read the best blockchain payment processors for fintechs first.
Open the BlindPay payout quickstart, pick your flavor, and send a USDB test payout today.
FAQ
More payments articles
See all payments articlesHow to add blockchain payments to a mobile fintech app: architecture, onboarding, quotes, and push notifications
Add stablecoin payins and payouts to an iOS or Android app: keep API keys on your server, onboard users in-app, show live quotes, and push webhook updates.
On/off ramp liquidity with live quotes: how it works and why it matters
What on/off ramp liquidity with live quotes means for developers: live quote vs batch rate, the quote ID flow in an API, what happens on expiry, how liquidity depth changes spread by transaction size, rate windows and UX, and a checklist for evaluating a live quote API.
Coinbase Onramp and Stripe's crypto onramp vs a business off-ramp API
Coinbase Onramp and Stripe's onramp sell crypto to consumers. A business off-ramp API pays a company's recipients over local rails. How they compare.