USDC Checkout Integration
A grounded engineering guide to integrating USDC payments at checkout — the API patterns, the network choice, the webhook idempotency, and the failure modes you'll meet in production.
TL;DR
A USDC checkout integration follows a familiar pattern: create a payment intent on your server (amount, currency, network preferences), receive a hosted checkout URL or widget config from your acquirer, let the buyer pay in their wallet, and handle the confirmation webhook idempotently. The engineering decisions that matter most are network selection (default to Solana or Base for sub-cent fees), webhook idempotency (use order ID + tx hash), refund flow (collect wallet address at checkout, expose refund API), and graceful UX for the wallet-signing step (deep-link mobile wallets, fall back to QR for desktop). Done right, USDC checkout completes in under 30 seconds end-to-end and costs the merchant 0.3%–1% depending on the acquirer.
The Standard API Shape
USDC acquiring APIs broadly follow the payment-intent pattern that Stripe popularised. The minimum surface:
Server creates payment intent
POST /v1/paymentswith body containingamount,currency(USD if quoting in fiat; USDC if quoting in stablecoin),settlement_preference(USDC vs fiat),networks(allowed list),order_id(your idempotency key),success_url,cancel_url, and ametadatablob for your own reconciliation.Acquirer returns checkout URL or widget config
For hosted checkout: a URL the buyer is redirected to. For embedded widget: a client-side config (publishable key, intent ID) the widget loads. Both flows handle wallet detection, network selection, and quote display.
Buyer signs in wallet, on-chain transfer broadcasts
Mobile: deep-link into MetaMask, Phantom, Trust, etc. Desktop: WalletConnect QR or browser-extension popup. The buyer reviews the amount and signs.
Acquirer waits for confirmations, fires webhook
POST your_webhook_urlwith payload containingevent_type: “payment.succeeded”,order_id,amount,tx_hash,network, and a signature you verify with the acquirer’s webhook secret.Server marks order paid idempotently
Verify webhook signature, look up
(order_id, tx_hash)in your DB, mark paid if not already marked, dispatch fulfillment.
Network Selection
The choice of which networks to accept is a UX-cost trade-off:
- Solana: Sub-cent network fee, sub-second confirmation. Best default for small AOV.
- Base: A few cents network fee, a few seconds confirmation. Strong wallet support; good default for crypto-native audiences.
- Polygon, Arbitrum: Similar fees and speed to Base. Fine to add but not strictly necessary.
- Ethereum mainnet: $1–$8+ network fee depending on gas. Painful for buyers on small orders. Accept for high-AOV (>$500) or as a fallback.
- Tron: If you’re targeting Asia/Africa USDT flows, supporting USDT-TRON alongside USDC is worth considering.
Webhook Idempotency
Webhooks retry. Your handler must be idempotent. The standard pattern:
- Verify the webhook signature using HMAC-SHA256 with your acquirer’s secret.
- Extract
(order_id, tx_hash)from the payload. - Look up that pair in your
processed_paymentstable. - If found: return 200 OK without doing anything else.
- If not found: insert the row (with a UNIQUE constraint on the pair), then mark the order paid in your main order table, then dispatch fulfillment.
- Wrap steps 5 in a single DB transaction so a crash mid-flow doesn’t double-fulfill.
Most acquiring webhooks include a delivery ID — log it for debugging, but don’t rely on it as the idempotency key. Network confirmations and acquirer retries can produce duplicate payment.succeeded events with different delivery IDs but the same tx_hash; your dedup must be on the on-chain identity, not the delivery.
Refund Flow
Two patterns dominate:
- Refund-to-sender: The acquirer sends USDC back to the address the buyer originally paid from. Cleanest UX; works because the on-chain payment record contains the source address.
- Refund-to-specified-address: The buyer specifies a refund destination (might differ from the source if they paid from an exchange or hot wallet). Most acquirers support this via a
POST /v1/refundswithdestination_address.
The merchant pays the network fee on the refund leg. Full and partial refunds are both supported. The refund itself takes the same on-chain confirmation time as the original payment.
Production Failure Modes
- Buyer abandons after signing. The on-chain transfer is broadcast but they close the tab before the webhook fires. The acquirer still detects and confirms the payment via on-chain monitoring; your fulfillment runs from the webhook, not from the redirect.
- Buyer pays the wrong amount. Most acquirers reject mismatched amounts at on-chain detection time. Some allow approximate matches (within a tolerance) and surface the discrepancy in the webhook.
- Buyer pays on the wrong network. If they sign on Ethereum but you only accept Solana, the payment misses your acquirer’s monitoring entirely. Most providers return the funds via a recovery flow; some lose them. Make the network selection clear at checkout.
- Buyer pays from an exchange address. Refunds-to-source may bounce or land in a wrong account. Collect a refund destination address explicitly at checkout for any product where refunds are likely.
- Stablecoin depeg. Rare but real. If USDC depegs significantly during the brief window between quote and on-chain settlement, the merchant or acquirer absorbs the difference depending on the agreement.
Add USDC checkout with DPT Acquiring
From 0.3% per transaction. Same-day settlement in USDC or fiat. Hosted checkout URL or embeddable widget. SDK and webhook docs at merchant.dpt.xyz.