One-cent gaps between your math and a stablecoin payout come from token decimals, whole-unit rounding, and recomputed fees. How to reconcile each.
A stablecoin payout ends up one cent away from your own math for three reasons: the token has more decimals on-chain than the API amount, some currencies settle in whole units and get rounded down, and recomputed rates or fees use a different rounding rule than the provider. Read amounts from the quote, store integers, and the gaps disappear.
This is a reconciliation guide. The basics of why payment APIs take integers live in stablecoin API quotes explained. Here the question is narrower: your ledger says 6,633.47 and the bank statement says 6,633.46. Which one is wrong?
Key takeaways
receiver_amount from the rate. Store what the quote returns.Almost always from one of three conversions. Each has a different fix.
| Source | What happens | Typical gap | Fix |
|---|---|---|---|
| Token decimals | API amount has 2 decimals, on-chain amount has 6 | Sub-cent dust | Convert with integer math, never floats |
| Whole-unit rails | MXN, COP, ARS payer amounts rounded down to a whole unit | Up to 0.99 of a peso | Show and store the quoted sender_amount |
| Recomputed math | Your rate times amount uses another rounding rule | 1 minor unit | Read receiver_amount, don't derive it |
| Float storage | 0.1 + 0.2 drifts in binary floating point | Random, grows with volume | Integers or decimal strings only |
Most teams hit the last two first, because they show up in spreadsheets.
Two decimals in the API, six on the chain. BlindPay's request_amount, sender_amount, and receiver_amount are integers in minor units: 10000 is 100.00 (payout quotes).
The on-chain side is different. The same docs show a payout quote with sender_amount: 10000 returning a contract.amount of "100000000" for the ERC-20 approve. That's 100 × 1,000,000. So for USDC:
Two things to notice. contract.amount is a string, on purpose: on-chain integers can exceed what a JavaScript Number holds exactly, which is why MDN documents BigInt for arbitrary-size integers (MDN). And you never need to compute it yourself. Pass the payload from the quote straight to your wallet library. Executing a payout quote on EVM, Solana, and Stellar covers what each chain signs.
Wallet balances can hold dust below one cent (0.000001 USDC is a valid amount). Your ledger works in cents. Expect a few base units of difference between a raw wallet balance and the sum of your recorded payouts, and don't chase it.
Because those rails settle in whole currency units. On a payin quote where currency_type is sender and the currency is MXN, COP, or ARS, request_amount must be a multiple of 100. 10050 fails with request_amount_must_be_a_whole_currency_unit (payin quotes).
The receiver side is where the cent goes missing. If you quote by the stablecoin amount (currency_type: receiver), BlindPay computes the peso amount and rounds it down to the nearest 100 minor units before returning sender_amount. The docs give the reason: a fractional amount would be truncated on arrival at the bank, and the deposit could come back as an invalid payment amount.
Illustrative example. You want a customer in Mexico to fund 500.00 USDC.
| Line | Value |
|---|---|
| Requested stablecoin | 500.00 USDC (request_amount: 50000, currency_type: receiver) |
| Illustrative rate | 18.4567 MXN per USDC |
| Exact conversion | 500 × 18.4567 = 9,228.35 MXN |
sender_amount returned | 922800 (9,228.00 MXN, rounded down) |
| Difference | 0.35 MXN |
If your checkout shows "9,228.35 MXN" from your own math, the payer sends 35 centavos more than the quote expects, or your UI and the bank instruction disagree. Show sender_amount from the response. Read receiver_amount too, rather than assuming exactly 500.00 lands.
Because rounding rules disagree at the half cent. The rate in a quote has more decimals than the currency, so every conversion produces a fraction that someone has to round.
Illustrative example. A payout of 250.00 USDC at a blindpay_quotation of 5.3713 BRL per USDC:
| Method | Calculation | Result |
|---|---|---|
| Exact product | 250.00 × 5.3713 | 1,342.825 BRL |
| Round half up | .825 → .83 | 1,342.83 |
| Round half even (banker's) | .825 → .82 | 1,342.82 |
| Truncate | .825 → .82 | 1,342.82 |
Three reasonable methods, two answers. Python's decimal module defaults to ROUND_HALF_EVEN and offers eight rounding modes (Python docs), and spreadsheets often default to half up. Neither is wrong. They just aren't the provider's.
The quote already did this math. receiver_amount is the number the bank account receives, after the rate, the flat_fee, any partner_fee_amount, and the billing_fee_amount when billing charges are on. Reconcile against it. If you want to measure the price, compare commercial_quotation with blindpay_quotation as rates, not by re-deriving amounts; how to read an FX quote shows how.
Integers for minor units, decimal strings for exact values, floats never. JavaScript shows the problem in one line: 0.1 + 0.2 is 0.30000000000000004 (MDN). Sum ten thousand payouts in floats and the error stops being theoretical.
A storage pattern that holds up:
sender_amount, receiver_amount, flat_fee, partner_fee_amount as integer columns, next to the quote_id."5.3713", not 5.3713. Multiply with a decimal library when you need to.amount in cents and the exact token_amount as a decimal string (changelog). Match the ledger on amount, keep token_amount for explorer lookups.Webhooks and reconciliation covers matching these rows to payout events.
Match on the quote, not on your math. The receiver_amount on the quote is the expected credit in minor units. Your own record links the payout id (po_...) to the quote_id you executed, and webhooks tell you when it completes.
A reconciliation check that catches real problems and ignores rounding noise:
receiver_amount. Exact match required.sender_amount. Exact match required against your ledger.If an exact field is off by more than rounding, that's a real exception. Check the bank details, the payout status, and the tracking fields before anything else. Payout quality metrics shows how to log quoted vs. delivered amounts per payout so these show up on a dashboard instead of in a month-end spreadsheet.
Pull last month's payouts, compare your stored amounts with each quote's sender_amount and receiver_amount, and count the one-cent gaps. Every gap points at a place your code recomputes. The payout quotes docs list each amount field.
Seven places blockchain payments beat bank rails: contractor payroll, remittances, B2B suppliers, marketplaces, 24/7 treasury, bill pay, and PSP payouts.
Push means the payer sends an ACH transfer. Pull means you debit a connected account. How each funds a USD to USDC on-ramp, the fees, and the return risk.
AP2, ACP, and x402 each verify that an AI agent had permission to spend. Here is what every protocol covers, who backs it, and the reconciliation gap none of them close.