Setting Up Recurring USDC Billing with Caps and Retries
Learn how to set up recurring USDC subscription billing with a spending cap, and handle failed onchain payments and retries using Reclack's non-custodial flow.

Recurring payments are a critical part of SaaS and API businesses. In crypto, the mechanics are different from cards. You need to set up a capped approval, handle onchain settlement, and manage failures with retry logic. This guide walks through how to implement recurring USDC subscription billing with a spending cap, and how to handle failed payments in a non-custodial crypto flow.
How Recurring USDC Payments Work in Crypto
Recurring billing in crypto requires explicit authorization each time, or a smart contract that enforces the cap. Unlike cards, where a single approval may last months or years, crypto wallets need users to sign a transaction or provide a bounded approval for the billing contract. The cap is always visible to the user, who signs it in their wallet interface.
Settlement is final as soon as the transaction is confirmed onchain. There are no chargebacks. If a payment fails (for example, due to insufficient funds or a revoked approval), you need a plan to retry or notify the customer.
Components of a Recurring Subscription Flow
A working subscription billing flow onchain has these elements:
- Checkout: The user is presented with a subscription offer (amount, interval, cap).
- Authorization: The user signs a capped approval for USDC transfers from their wallet.
- Scheduled Collection: At each interval, your backend (or a decentralized agent) attempts to collect the due amount, up to the cap.
- Failure Handling: If a collection fails, trigger retries or a dunning workflow.
Example Subscription: $20/month with $60 Cap
Suppose you offer a developer API for $20/month, billed in USDC, with a quarterly cap of $60. The flow is:
- User sees the subscription offer: 20 USDC per month, not to exceed 60 USDC in any 3-month period.
- User connects their wallet and signs an ERC-20 approval for 60 USDC to your billing contract or settlement address.
- Each month, your backend calls a collection function for 20 USDC.
- If the transfer succeeds, the user’s remaining allowance is reduced. If not, you handle the error and retry.
Implementing the Approval and Collection
Step 1: Present the Subscription and Cap
Let the user see exactly what they are approving. For example:
- Plan: API Pro
- Amount: 20 USDC per month
- Cap: 60 USDC max per 3 months
Step 2: Request a Bounded Approval
Use the ERC-20 approve() function. The user will sign a transaction from their wallet, authorizing your contract to spend up to the cap.
// For USDC (ERC-20), approval from user to your contract
IERC20(usdcAddress).approve(billingContractAddress, 60 * 1e6); // USDC uses 6 decimals
You must never ask for an unlimited approval. The cap is explicit and the user signs it.
Step 3: Collect a Subscription Payment
On each billing date, call transferFrom() to move the monthly amount from the user’s wallet to your settlement address. This requires the previous approval to be in place.
// Called by your backend or a bot
IERC20(usdcAddress).transferFrom(userAddress, merchantSettlementAddress, 20 * 1e6);
If the wallet has sufficient USDC and the cap is not exceeded, this will succeed. Onchain settlement is final upon confirmation.
Handling Failed Collections
Payment failures can occur for several reasons:
- The user’s wallet has insufficient USDC balance.
- The user has revoked or reduced the approval.
- The cap has been reached and not renewed.
Your backend must detect these failures. For example, the transferFrom call will revert if any of the above conditions are true.
Retry Logic: How and When to Retry
A typical retry strategy might be:
- Retry the collection once per day for up to 7 days.
- After each failure, notify the user (email, push, or onchain message).
- After 7 failed attempts, mark the subscription as lapsed.
Example: Retry in Node.js
Suppose you have a Node.js backend using ethers.js:
const { ethers } = require('ethers');
const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; // USDC on Ethereum
const merchantSettlement = '0xYourMerchantSettlementAddress';
const user = '0xCustomerWalletAddress';
const amount = ethers.utils.parseUnits('20', 6); // 20 USDC, 6 decimals
async function tryCollect(provider, signer) {
const usdc = new ethers.Contract(usdcAddress, [
'function transferFrom(address from, address to, uint value) returns (bool)',
'function allowance(address owner, address spender) view returns (uint256)',
'function balanceOf(address) view returns (uint256)'
], signer);
const balance = await usdc.balanceOf(user);
const allowance = await usdc.allowance(user, merchantSettlement);
if (balance.lt(amount) || allowance.lt(amount)) {
return { success: false, reason: 'Insufficient funds or approval' };
}
try {
const tx = await usdc.transferFrom(user, merchantSettlement, amount);
await tx.wait();
return { success: true };
} catch (e) {
return { success: false, reason: e.message };
}
}
You can schedule this function to run daily, with logic to track the number of retries and notify the user.
User Experience: Notifying of Failure
Each failed attempt should trigger a clear notification. This might be by email or web push, and should include:
- The reason for failure (insufficient funds, approval needed).
- A link to renew approval or add funds.
- The date of the next retry.
Onchain, you can also surface this in a customer portal or dApp dashboard.
Expiring and Renewing the Cap
When the cap is fully used or the time window expires, prompt the user to renew the approval. Never attempt to collect more than the signed cap. The user must always see and sign any new approval.
Full Example: End-to-End Subscription Setup
Suppose you offer a $20/month API, with a $60 cap per 3 months. Here’s the process, step by step:
- Offer and Approval:
- Show the user the plan: $20/month, $60 max per 3 months.
- User signs
approve()for 60 USDC to your contract.
- Monthly Collection:
- Each month, call
transferFrom()for 20 USDC from the user. - Onchain, this moves funds directly to your settlement address.
- Each month, call
- Failure Handling:
- If the transfer fails, log the reason, notify the user, and schedule a retry.
- After the cap is used or expired, prompt for a new approval.
No funds ever sit with Reclack or in an intermediary contract. You only collect what was approved, and every transaction is final once confirmed.
Security and Finality Considerations
- No Seed Phrases: Users sign transactions from their own wallets. Never ask for a seed phrase or private key.
- No Unlimited Approvals: Always set a cap. The user must see and sign it.
- Final Settlement: Once a transaction is confirmed, there are no chargebacks or reversals. If a payment fails, retry or prompt for renewal.
Using Reclack for Recurring USDC Billing
At Reclack, we handle all of these mechanics for you. Merchants configure plans, intervals, and spending caps in our dashboard or API. Our hosted checkout guides the user through capped approvals in their own wallet. We handle recurring collections, retries, customer notifications, and provide a portal for users to manage approvals and billing. Funds settle directly from customer wallets to your address. No custody, no chargebacks, no hidden approvals. To learn more or start integrating, visit reclack.com.

