Skip to content
onchain-engineeringstablecoin-billing
Joshua C.

What Onchain Recurring USDC Approvals Actually Sign

How do onchain recurring USDC approvals work? We explain what is signed, how ERC-20 spending caps are set, and what happens in a real recurring billing flow.

What Onchain Recurring USDC Approvals Actually Sign

Recurring billing is a fixture for SaaS, APIs, and digital services, and stablecoin payments now bring that model onchain. For developers and operators, the mechanics of recurring USDC payments are not the same as pulling from a card or direct debit. The approval step is critical. What exactly does the customer sign, what does the merchant get, and what are the limits?

How ERC-20 Approvals Work in Recurring Billing

Most onchain recurring payment flows use the ERC-20 approve pattern. The customer approves a smart contract to spend up to a capped amount of USDC from their wallet. This is not an unlimited authorization. The cap is explicit, visible, and enforced by the ERC-20 standard. The contract cannot spend more than the cap without a new approval.

This is different from recurring card billing, where a merchant can charge the card any amount, subject to network rules. Onchain, the cap is set on the blockchain and cannot be exceeded by the merchant or the contract.

What the Customer Signs

The customer signs a transaction that calls the approve function of the USDC contract. The key parameters are:

  • The address of the smart contract (the spender)
  • The amount (the spending cap)

For example, if a SaaS subscription is $30/month, the contract might ask for a $90 cap (three months). The customer reviews the cap and signs the approval. This is a one-time action, unless the cap is reached or revoked.

Example approval transaction:

IERC20(usdc).approve(
    recurringBillingContractAddress,
    90_000_000 // $90 in USDC with 6 decimals
);

No seed phrase or private key is ever shared. The customer signs this transaction in their wallet. The approval is visible onchain.

What the Merchant Can and Cannot Do

With the approved cap, the merchant’s contract can initiate transferFrom transactions within the approved amount. Each payment is a separate transaction. If the merchant tries to pull more than the cap, the transferFrom will revert. There is no way for the contract to increase its own allowance. Only the customer can increase or renew it.

There are no chargebacks. Once a transferFrom is confirmed, the payment is final. If a payment fails, because the cap is used up, the customer revokes the approval, or there is insufficient balance, the contract cannot force a retry. The flow must ask for a new approval or retry later.

Worked Example: A SaaS Subscription

Suppose you run an analytics SaaS that charges $40/month in USDC. You use a recurring billing contract on Base. Here is what happens:

  1. Checkout:

    • The customer connects their wallet.
    • The UI prompts them to approve the billing contract to spend $120 USDC (three months’ cap).
    • The customer signs the approval transaction.
  2. Monthly Billing:

    • Each month, your backend calls the contract to pull $40 USDC from the customer’s wallet using transferFrom.
    • After three months or three charges, the cap is used up. The contract cannot pull more.
  3. Renewal:

    • The UI (or automated reminder) prompts the customer for a new approval.
    • The cycle repeats.

If the customer revokes approval or their balance drops below $40, the next charge fails. Your system should notify the customer and prompt for action.

Key point: The customer never approves more than the cap. There is no unlimited drain risk from the contract.

Revoking and Adjusting Spending Caps

Customers can revoke or reduce approval at any time. Most wallets offer a UI for managing token allowances. If approval is revoked before all charges are made, the merchant’s contract cannot collect further payments. This is an important control for customers. Your billing logic should check allowance and balance before each charge to handle this gracefully.

Code: Checking Allowance Before Charging

A robust contract or backend integration checks both allowance and balance before attempting a pull.

uint256 allowance = IERC20(usdc).allowance(customer, address(this));
uint256 balance = IERC20(usdc).balanceOf(customer);
if (allowance >= chargeAmount && balance >= chargeAmount) {
    IERC20(usdc).transferFrom(customer, merchant, chargeAmount);
} else {
    // Notify customer or retry later
}

What Actually Settles: Onchain Finality

Every payment is an onchain transaction. Once confirmed, the USDC moves from the customer’s wallet to the merchant’s settlement address. There is no way for the merchant to reverse or refund the payment from the contract. If a refund is needed, it must be a new, explicit transaction in the other direction.

No party can claw back funds. There is no dispute window. Finality is determined by the blockchain. This differs from offchain billing, where settlements can be reversed or frozen.

Practical Implications for SaaS and API Billing

  • Customers see and control the maximum exposure, not a blank check.
  • Merchants need to handle cap exhaustion and approval renewals as part of the lifecycle.
  • Failed charges are final. There is no forced collection. Customer action is required to renew or fix failed payments.
  • Refunds are always explicit new transactions.

This model gives both sides transparency and control, but demands clear UI and robust backend handling of edge cases.

We built Reclack to make these flows reliable and predictable for both merchants and customers. We never hold funds or ask for a private key. Every recurring billing authorization is capped and visible. See more about how we handle recurring stablecoin billing at reclack.com.

onchain-engineeringstablecoin-billing