Skip to content
onchain-engineeringstablecoin-billing
Joshua C.

Handling Failed Stablecoin Payments: Best Practices Onchain

Understand the mechanics of failed onchain USDC payment attempts, how to diagnose errors, when to retry, and best practices for stablecoin billing recovery.

Handling Failed Stablecoin Payments: Best Practices Onchain

Stablecoin payments onchain are simple in theory: a customer authorizes a transaction, the funds settle, and the merchant is paid. In practice, failures are part of the system. Failed onchain payments are not rare, and your billing workflow needs to recognize and react to errors. A single missed payment can signal a user abandoning your service, an error in your integration, or simply a temporary issue with gas. This post walks through how and why stablecoin payment attempts fail, with examples and code, and what to do when it happens.

Why Onchain Stablecoin Payments Fail

Stablecoin payments, such as USDC transfers, can fail for a few root reasons:

  • Insufficient funds in the payer’s wallet
  • Insufficient gas (ETH, MATIC, or other native asset) to pay for the transaction
  • Allowance or approval too low, or not granted
  • Contract or network errors (nonce issues, chain congestion, reorg)
  • User rejection in the wallet

If you are building a recurring billing system or collecting one-time invoices, you need to detect these cases and handle them gracefully. Each failure type has a different recovery path.

Detecting and Diagnosing Payment Failures

Onchain, payment attempts resolve to either a confirmed transaction or nothing. A failed attempt is not always a broadcasted transaction. Sometimes, the user never signs, or the wallet outright rejects your request.

For example, suppose your dapp requests a USDC transfer on Polygon, but the user wallet has only 0.003 MATIC and the average gas cost for the transfer is 0.01 MATIC. The transaction will never be confirmed. If the dapp does not check for gas before prompting, the user may see an error in their wallet, or the transaction fails silently.

To diagnose, always: - Check the latest transaction receipt after submission - Listen for onchain events, not just UI callbacks - Parse error messages from the wallet provider

Example: A Failed USDC Transfer on Polygon

Suppose you attempt to collect a 50 USDC payment from a customer’s wallet (0xabc…) to your settlement address (0xdef…) on Polygon.

You submit the transfer:

const tx = await usdcContract.transfer(
  merchantAddress, // 0xdef...
  ethers.utils.parseUnits('50', 6) // 50 USDC with 6 decimals
);
await tx.wait();

But the user only has 10 USDC in the wallet.

The transaction will revert. The error you get from ethers.js looks like:

{
  "code": "CALL_EXCEPTION",
  "reason": "ERC20: transfer amount exceeds balance",
  "transactionHash": "0x..."
}

If you catch this, you know to notify the user, log the attempt, and do not retry until the balance increases.

ERC-20 Approval Too Low

If your workflow requires an allowance, and the customer approved only 20 USDC in a recurring cap, your 50 USDC transfer will revert. The error is different but similar:

{
  "code": "CALL_EXCEPTION",
  "reason": "ERC20: transfer amount exceeds allowance",
  "transactionHash": "0x..."
}

Your logic needs to handle both cases distinctly: balance vs allowance.

Responding: When to Retry, When to Notify

Not all failures are equal. Some can be retried, some require user action. Here are practical rules:

Retry Automatically

  • Gas price too high: wait and retry, or sponsor gas if possible
  • Transient network failures: nodes down, chain congestion

Require User Action

  • Insufficient stablecoin balance: notify user, wait for a top-up
  • Insufficient approval: prompt user to re-approve, with a clear cap
  • User rejected in wallet: show a reminder and explain why the payment is needed

Log Every Failure

Always log failed attempts with the error, timestamp, and context. This is critical for both customer support and debugging your integration.

Real Workflow: Subscription Payment Attempt with Retries

Suppose you run a SaaS tool that charges $10 in USDC every month on Base. The customer has granted a recurring cap of $20.

On payment day, you attempt the transfer. The transaction fails due to insufficient ETH for gas.

A naive retry loop is not enough. Instead, implement backoff logic, and notify the user after the second failure. Here is a simplified approach in pseudocode:

def attempt_payment(user, amount):
    for i in range(3):
        try:
            tx_hash = send_usdc(user.wallet, merchant_wallet, amount)
            receipt = wait_for_confirmation(tx_hash)
            if receipt.status == 1:
                return 'success'
        except InsufficientGas:
            sleep(60 * (i+1))  # Exponential backoff
            continue
        except (InsufficientBalance, AllowanceTooLow):
            notify_user(user, 'Please add funds or increase approval.')
            return 'failure'
    notify_user(user, 'Payment failed after multiple attempts.')
    return 'failure'

This approach avoids spamming the chain, respects user caps, and prompts for action only when the user can resolve the problem.

What Not to Do

  • Never assume a failed transaction can be reversed: onchain settlement is final once confirmed.
  • Do not retry the same failing transaction endlessly. Recognize when to escalate to the user.
  • Do not request unlimited approvals or custody. Always work with explicit, bounded caps.

How We Handle Failed Payments at Reclack

At Reclack, we track every attempted USDC payment, including the error returned by the chain or wallet. When a payment fails, we classify the cause. If it is a gas or network error, we retry on a schedule with backoff. If the issue is balance or approval, we notify the customer through the portal and by email, with a clear explanation of what is needed. All recurring authorizations are capped, so we never request more than the customer has approved. Every settled transaction is final. You can read more about how we approach non-custodial stablecoin billing at Reclack.com.

onchain-engineeringstablecoin-billing