Back to Blog
Guides
2026-10-0113 min readBy StableOps

Stablecoin Checkout: Integrate USDC and USDT Payments

Build a stablecoin checkout for USDC and USDT with clear payment instructions, wallet flows, finality, webhooks, exception handling, and conversion tracking.

Stablecoin checkout
USDC checkout
USDT checkout
Payment API

A stablecoin checkout turns a business order into precise onchain payment instructions, helps a customer pay in USDC or USDT, and gives the merchant backend verified evidence for fulfillment. A complete integration connects amount, token, network, destination, expiry, wallet interaction, and payment status. A connect-wallet button covers only one part of that flow.

If you are still choosing an acceptance model, start with the business guide to accepting stablecoin payments. This article focuses on the next decision: how to build the customer journey, recover interrupted attempts, and release an order safely.

What is a stablecoin checkout, and which integration model fits?

A stablecoin checkout connects an order to a wallet transfer and merchant fulfillment. A payment link is an entry point, a wallet signs and submits the transfer, and a payment API creates orders and reports their state. Those components have different responsibilities.

Evaluate interface hosting separately from custody. A provider can operate the payment page while the stablecoin goes directly to a merchant-controlled address. StableOps uses that model: the merchant keeps the private keys and signs refunds. It does not provide automatic fiat conversion or bank settlement.

Integration modelWhere the customer paysMerchant implementationUseful whenValidate before choosing
Hosted checkoutProvider-operated pageBackend creation, redirect, events, order recoveryA standard payment journey meets the product needBranding, mobile wallets, return paths, custody, exception states
Embedded componentComponent inside the merchant siteComponent integration, page state, backend order and fulfillmentStaying on site matters and component limits are acceptableWallet compatibility, loading failures, mobile browsers, upgrade costs
Custom payment API flowMerchant-operated payment interfaceInstructions, wallet adapters, status queries, recovery, fulfillmentThe business needs a specialized journeyToken identity, precision, manual payments, maintenance effort

These are general integration models, not a claim that every provider ships all three. StableOps offers hosted sessions, while a custom interface can use Payment Orders and the wallet SDK. An embedded experience requires a separate assessment of the component and the work your team must own.

Use stablecoin payment links for a public, fixed-price entry point. Create a one-time session for carts, customer-specific prices, or dynamic taxes. For accounts receivable, define the invoice-to-payment relationship before choosing the checkout surface.

What should a USDC or USDT checkout display?

Give the payer one complete instruction they can inspect before signing and recognize after returning from a mobile wallet. Critical facts should remain visible without a hover tooltip or a particular wallet extension.

Checkout informationRequired presentationFailure it prevents
Product and orderProduct, merchant identity, support referencePaying the wrong business order
Exact payable amountReturned order.amount and tokenSending the base amount after automatic amount allocation
Network and environmentFull network name and mainnet or testnet labelSending the right symbol on the wrong chain
Token identityInspectable contract or mint addressConfusing native, bridged, or unrelated same-symbol tokens
DestinationFull address from the selected active instructionCopying another chain's or expired attempt's address
ExpiryServer-returned order.expiresAt and countdownTreating a locally extended timer as a valid payment window
FeesRequired net receipt and who pays network or withdrawal feesDeducting a withdrawal fee from the amount owed
State and helpWaiting, submitted, detected, confirming, final, support contactCalling a transaction hash a completed payment

In a StableOps Payment Order, amount and expiry are top-level fields. paymentInstructions supplies candidate chain, asset, and address combinations. Do not read amount or expiry from an instruction, or combine fields from different candidates.

Identify the asset by its network and contract, not its symbol. Circle's USDC contract list separates mainnet and testnet identities. Tether's supported protocols lists USD₮ information by network. Issuer support does not establish support in your payment provider or wallet, so verify your own allowed combinations too.

With amountMode: 'auto', StableOps may adjust the amount by a token's smallest unit to resolve a shared-address conflict. Hosted checkout displays checkout.paymentOrder.amount; custom checkout must use the same returned value. exact preserves the fixed amount. Both modes require exact payment and do not automatically accept short, excess, or split transfers.

How do you create a checkout session on the server?

Authenticate the customer and validate the price and business order before creating a payment attempt. Persist the idempotency key and the entire creation request. Retrying that attempt must reuse both, rather than reserving another address whenever the page reloads.

This minimal Next.js handler uses merchant-owned database functions. loadAuthorizedCheckoutAttempt must verify order ownership and persist the request snapshot on first creation. saveCheckoutMapping saves the relation needed for event handling. Neither function is a StableOps SDK method. Use a Sandbox API key and Base Sepolia USDC for this example.

// app/api/checkout/route.ts
import { StableOps } from '@stableops/api-sdk'
import {
  loadAuthorizedCheckoutAttempt,
  saveCheckoutMapping,
} from '@/lib/checkout-store'

export const runtime = 'nodejs'

const stableops = new StableOps({
  apiKey: process.env.STABLEOPS_API_KEY!,
})

export async function POST(req: Request) {
  // Load the persisted amount, expiry, and complete request snapshot.
  const attempt = await loadAuthorizedCheckoutAttempt(req)
  const checkout = await stableops.checkoutSessions.create(
    attempt.checkoutRequest,
    { idempotencyKey: attempt.id },
  )

  if (!checkout.url) return new Response('checkout unavailable', { status: 502 })

  await saveCheckoutMapping({
    attemptId: attempt.id,
    checkoutSessionId: checkout.id,
    paymentOrderId: checkout.paymentOrder.id,
    requestedAmount: checkout.paymentOrder.requestedAmount,
    payableAmount: checkout.paymentOrder.amount,
  })

  return Response.json(
    { url: checkout.url },
    { headers: { 'Cache-Control': 'no-store' } },
  )
}

Persist attempt.checkoutRequest with fields like these when the attempt is first created. Values come from the authorized order and server configuration, not a browser-supplied final price or arbitrary redirect destination.

import type { CreateCheckoutSessionInput } from '@stableops/api-sdk'

const checkoutRequest: CreateCheckoutSessionInput = {
  merchantOrderId: attempt.merchantOrderId,
  amount: order.amount,
  amountMode: 'auto',
  acceptedAssets: [{ chain: 'base-sepolia', asset: 'USDC' }],
  expiresAt: attempt.expiresAt,
  title: order.title,
  successUrl: order.successUrl,
  cancelUrl: order.cancelUrl,
  walletConnectProjectId: process.env.WALLETCONNECT_PROJECT_ID || undefined,
}

The snapshot includes amount, title, expiry, allowed assets, redirects, and WalletConnect configuration. Recomputing the expiry or reading changed configuration on a retry produces a different request under the same key. Once an attempt expires, verify that the business order remains unpaid, then create a distinct attempt and key while preserving every attempt's mapping to that order.

The frontend can redirect with window.location.assign(url). A one-time URL contains the clientSecret required to access that payment session. Give it only to the current payer and keep it out of shared caches, public order listings, and analytics.

Merchant backend         Checkout and payer wallet        Chain and payment operations
Persist order + attempt
Create session --------> Display complete instructions
                         Customer checks and signs ------> Broadcast transfer
                         Show submitted                    Detect, match, confirm
Verify and dedupe <--------------------------------------- payment.finalized
Commit order update + durable fulfillment job
Fulfill idempotently --> Customer order page shows server result

The return page reads the merchant backend's stored order state. successUrl and cancelUrl are browser destinations, not authority to mark an order paid or cancel a broadcast transfer. The Next.js USDC integration guide covers the complete application structure.

How should checkout recover wallet errors and interrupted payments?

Preserve what already happened and avoid unnecessary repeat transfers. First distinguish an attempt that has not broadcast from a transaction whose result is unknown. That distinction determines the recovery action.

SituationPayer feedbackRecovery behavior
No installed or detected walletOffer a mobile or manual routeKeep the attempt rather than requiring a specific installation
Customer rejects connection, network switch, or signingSay the action was canceledWait for a deliberate retry instead of reopening prompts
Wrong wallet networkShow the required network nameRecheck account, network, and balances after switching
Network-switch request is pendingAsk the payer to finish in the walletDisable duplicate switch requests
Insufficient native fee balanceExplain token balance and fee-asset needs separatelyLet the payer fund the wallet without reducing the amount owed
Broadcast succeeded, but the page closed or a query timed outShow that payment is being checkedRestore the attempt and query the backend before suggesting another transfer
Payment window ended without detectionExplain that these instructions expiredStop presenting them as payable and investigate late transfers separately
Wrong token, wrong network, short or excess paymentShow that review is requiredPreserve evidence without advancing normal success state

MetaMask's network-management documentation distinguishes missing networks, rejected requests, and pending requests. Show a response appropriate to the cause. A wallet error also does not establish whether the server has detected a separate transfer.

Ethereum's gas documentation explains that its transaction fees are paid in ETH. Other networks have their own fee assets and rules. Keep that requirement separate from token balance and from exchange withdrawal fees. The stablecoin payment fee guide explains total payer and merchant costs.

For an unknown broadcast result, support should inspect the network, transaction hash, actual token, destination, and amount first. Closing the page, disconnecting a wallet, or clicking cancel cannot reverse an already broadcast transaction.

Why must verified webhooks authorize fulfillment?

Browser redirects and wallet receipts originate in the customer environment and are progress signals. Verified server events connect the matched order to its payment state. For shipping, irreversible access, and final ledger entries, a StableOps integration should wait for payment.finalized.

The handler below verifies the exact raw body before parsing and hands the event to durable storage. recordAndApplyPaymentEvent is a merchant implementation boundary, not an in-memory set or a direct call to a shipping API.

// app/api/webhooks/stableops/route.ts
import {
  EVENT_ID_HEADER,
  SIGNATURE_HEADER,
  verifySignature,
} from '@stableops/api-sdk/webhooks'
import { recordAndApplyPaymentEvent } from '@/lib/checkout-store'

export const runtime = 'nodejs'

export async function POST(req: Request) {
  const rawBody = await req.text()
  const verified = verifySignature({
    secrets: [process.env.STABLEOPS_WEBHOOK_SECRET!],
    header: req.headers.get(SIGNATURE_HEADER) ?? undefined,
    rawBody,
  })
  if (!verified.ok) return new Response('invalid signature', { status: 400 })

  const eventId = req.headers.get(EVENT_ID_HEADER)
  if (!eventId) return new Response('missing event id', { status: 400 })

  let event: unknown
  try {
    event = JSON.parse(rawBody)
  } catch {
    return new Response('invalid event body', { status: 400 })
  }

  // Validate event shape and order mapping, then commit event + business changes.
  await recordAndApplyPaymentEvent({ eventId, event })
  return new Response('accepted')
}

The storage boundary validates event structure and, in one transaction, inserts a unique event ID, checks the saved order mapping, and applies a legal state transition. That mapping must bind the saved Payment Order ID to the merchant's business reference, rather than granting access from a customer-supplied identifier. Only a first, verified payment.finalized event can insert an irreversible fulfillment job. Give that job a business-order uniqueness constraint and use the business order ID as the downstream idempotency key, so separate paid attempts cannot deliver the same order twice.

Duplicate events return success without side effects. A failed database commit returns failure so delivery can retry. Acknowledging first and retaining the work only in memory loses orders when the process crashes. The payment-confirmation guide explains the state boundary, and the webhook fulfillment guide covers atomic event storage and durable jobs.

How do you measure stablecoin checkout conversion?

Measure stages within the same created-session cohort, deduplicate by session, and set an observation cutoff. A browser visit, a payment attempt, and a business order are different denominators. Label the report accordingly.

This is a suggested merchant analytics model, not a list of fields in StableOps' built-in dashboard.

Suggested eventExact triggerRecording sourceQuestion it answers
checkout_startedBackend created the sessionMerchant backendHow many valid attempts began?
checkout_loadedPage retrieved that session's instructionsCheckout interfaceDid redirect or loading fail?
wallet_connectedCustomer approved wallet connectionWallet interfaceWhere does the wallet path lose customers?
transaction_submittedWallet returned a submitted transaction hashWallet interfaceWas a transfer attempted? This is not receipt evidence.
payment_detectedBackend matched a transfer to the orderVerified event or server queryDid the submitted payment match?
payment_finalizedPayment reached final fulfillment stateVerified event or server queryHow many payments completed?
order_fulfilledBusiness job completed and persisted its resultMerchant backendWhich paid orders remain undelivered?

Manual transfers can reach detection without a wallet-connection or frontend-submission event. Treat the table as stages you can observe, not a mandatory sequence for every payer. Do not record private keys, session secrets, or complete one-time checkout URLs in analytics.

For a hypothetical weekly cohort, suppose 1,000 distinct sessions were created, 900 loaded, 450 finalized by the cutoff, and 440 were fulfilled:

Page load rate = loaded sessions / created sessions = 900 / 1,000 = 90%
Payment completion rate = finalized sessions / created sessions = 450 / 1,000 = 45%
Paid-session fulfillment rate = fulfilled sessions / finalized sessions = 440 / 450 ≈ 97.8%

These are illustrative figures, not product performance. A production report also reconciles multiple attempts for one business order and tracks paid orders that still need fulfillment. More page interactions alone do not prove better payment outcomes.

StableOps currently records checkout stages including page viewed, session loaded, payment started, transaction submitted, and payment completed. See the Checkout documentation for its exact denominator and cohort. The suggested wallet-connected, payment-detected, and business-fulfilled stages may need your own instrumentation or server records. Marketing analytics is not a financial ledger.

What should you verify before launching a stablecoin checkout?

  • API keys and webhook secrets stay on the server, with separate Sandbox and Live configuration.
  • The backend validates customer identity, order ownership, price, and return destinations.
  • Creation retries reuse the entire persisted request, idempotency key, and expiry.
  • The interface shows the returned exact amount, network, token identity, destination, and deadline.
  • Mobile switching, rejected requests, insufficient fee balances, and manual payments have recovery paths.
  • An unknown broadcast result restores the original attempt rather than asking for an immediate repeat payment.
  • Raw-body verification, event deduplication, state changes, and fulfillment jobs share a durable boundary.
  • Late events cannot regress state, and separate attempts cannot fulfill one business order twice.
  • Wrong-network, wrong-token, amount, and late-payment exceptions retain evidence and a support route.
  • Visiting the success URL directly cannot ship an order.
  • Funnel reports specify denominator, deduplication, cutoff, and manual-payment behavior.

Use the three-layer crypto-payment testing method to validate these conditions before adding more networks. Start in the Sandbox Playground, observe one real testnet order, and connect the same instructions and final fulfillment boundary to your product using the Checkout documentation.

What are common questions about stablecoin checkout?

Does hosted checkout mean the provider holds the money?

Not necessarily. Interface hosting and custody are separate decisions. StableOps operates the checkout page while stablecoins reach merchant-controlled addresses. Private keys, treasury operations, and refund signing remain with the merchant.

Use reusable links for public, fixed-price services. Create one-time sessions when a cart, customer, invoice, or dynamic price must be bound to a specific attempt with its own order and state.

Must every payer connect a wallet?

No. Customers can transfer manually using the active order's instructions. StableOps hosted checkout provides browser-wallet and manual routes. The WalletConnect mobile entry requires session configuration and compatibility checks for the selected wallet and network.

Can one checkout accept both USDC and USDT?

Yes, with combinations actually supported by your payment service. The selected network, token, amount, and destination must belong to the same valid instruction. A token symbol alone cannot authorize a substitution.

Can I ship when the customer reaches the success URL?

No. Anyone can visit that URL, and a payer may return before finality. Authorize irreversible fulfillment from a verified, deduplicated payment.finalized event. The page displays the business state already stored by your backend.

Will processing continue after the payer closes the page?

A broadcast transfer can still be detected and confirmed, and webhook delivery does not depend on an open browser. Restore the server's order state when the customer returns instead of treating lost frontend state as a reason to pay again.

Does a checkout API automatically convert funds or issue refunds?

That depends on the provider's funds and settlement model. StableOps does not custody funds or automatically convert to fiat or settle to a bank. A merchant-signed refund is a separate transaction whose state must also be tracked.

Protocol sources and StableOps product behavior were checked on October 1, 2026. The snippets illustrate integration boundaries, and merchant database functions require implementation for your own order model. Recheck official token information, wallet compatibility, and current service support before going live.

Related articles

Learn how stablecoin invoicing works, issue bills payable in USDC or USDT, match on-chain payments, handle exceptions, and reconcile every paid invoice.

Learn how stablecoin payment processors handle checkout, confirmation, webhooks, and reconciliation, then compare fees, custody, APIs, and operational controls.

Calculate the true cost of USDC and USDT payments across network fees, provider pricing, treasury, off-ramp spreads, exceptions, and reconciliation.

Learn how to accept stablecoin payments with USDC or USDT, compare custody models, and launch reliable checkout, webhooks, refunds, and reconciliation.