Automating Onchain USDC Reconciliation with Internal Ledgers
Step-by-step guide to automating onchain USDC payment reconciliation with internal billing systems. Covers ledger sync, transaction mapping, code examples, and real data.

Crypto billing teams often bump into friction when syncing onchain USDC payments with their internal billing or accounting systems. Payments settle onchain, but subscription ledgers, invoices, and accounting rules live offchain. Manual reconciliation slows month-end close and creates risk. Automating that bridge saves hours and keeps your books accurate. This guide shows how to design and implement automated reconciliation between an onchain stablecoin ledger and an internal billing system, using USDC on Base, Ethereum, or Polygon as a working example.
Why Onchain Reconciliation Matters
Onchain payments are final when confirmed and visible to anyone, but your business logic is offchain. Subscription renewals, one-time invoices, usage billing, and refunds all need to match up with actual USDC receipts. If you cannot prove in your system that a payment was received, downstream logic, like renewals, feature unlocks, or accounting, breaks. Manual checks fail at scale. Automation makes reconciliation reliable and auditable.
What to Sync: Data and Events to Track
To automate reconciliation, you need to map each onchain payment event to a corresponding record in your internal billing system. The key data points are:
- Onchain transaction hash (tx hash)
- Amount settled (in USDC smallest units, typically 6 decimals)
- Timestamp (block time)
- Sender address (customer wallet)
- Recipient address (business-controlled settlement address)
- Status (confirmed, failed, pending)
- Reference metadata (optional: invoice ID, subscription ID, or memo)
A good reconciliation process always stores both the raw onchain event and its mapped internal billing object. This lets you audit later, even if something changes offchain.
Getting Onchain Payment Data: Sources and Patterns
There are three main ways to ingest onchain payment data for USDC collections:
1. Direct Node RPC or Third-Party APIs
You can listen for token transfer events using a node connection (Alchemy, Infura, QuickNode, or self-hosted). For USDC, filter for ERC-20 Transfer events where to equals your business settlement address. Some platforms also offer webhook notifications for new incoming transfers.
2. Indexers and Data Warehouses
If you need to reconcile at scale or across many addresses, use a blockchain indexer like Dune, The Graph, or a warehouse like Flipside. These let you query all transfers to your addresses, filter by time window, and join metadata.
3. Billing Platform Webhooks or Exports
If you use a billing system that supports onchain payments (such as Reclack), it may emit webhooks or exports for every relevant payment event. This removes the need to poll nodes or run your own indexer, and lets you tie the onchain event to the invoice or subscription directly.
Matching Onchain Payments to Internal Records
The core problem: how do you know which onchain payment matches which invoice, subscription, or customer? There are three common approaches:
1. Unique Settlement Address Per Invoice or Customer
Generate a new USDC receiving address for each invoice or customer. When a payment hits that address, you know exactly which object it applies to. This pattern is common in custodial or managed-wallet flows. For non-custodial systems, customers pay from their own wallet to a fixed merchant address, so this is only practical if you can manage multiple merchant addresses.
2. Reference Metadata in Payment (Invoice ID/Memo)
Ask the payer to include an invoice ID or subscription reference in the transaction data. For ERC-20 USDC, the standard transfer does not support arbitrary data. Some businesses use a transferAndCall pattern or batched multisend contracts, but this is not universal. In practice, most onchain SaaS billing ties the payment to the customer via sender address and payment amount.
3. Mapping by Amount, Sender, and Time Window
Map payments by scanning recent USDC transfers to your address, grouping by sender, and matching the amount to outstanding invoices. For example, if Alice is invoiced 100 USDC and you receive a 100 USDC transfer from her wallet within the invoice window, you mark the invoice as paid. This works for one-time invoices and recurring billing with fixed amounts. For variable usage or partial payments, add logic for under/overpayment.
Worked Example: Reconciling a USDC Invoice Payment
Suppose you issue an invoice #INV-2024-0032 to customer wallet 0xC0ffee...1234 for 250 USDC. Your merchant settlement address is 0xB0baF...5678. The invoice is due by June 30, 2024.
Onchain Payment
On June 25, you see this transfer on Base:
- Tx hash:
0xabc123...deadbeef - From:
0xC0ffee...1234 - To:
0xB0baF...5678 - Value:
250000000(250 USDC, 6 decimals) - Block timestamp:
June 25, 2024, 13:15 UTC
Internal Billing System
Your billing system stores invoice #INV-2024-0032 as:
- Customer: Alice, wallet
0xC0ffee...1234 - Amount due: 250 USDC
- Status: Pending
- Due date: June 30, 2024
Automated Reconciliation Logic
On a scheduled job or webhook trigger, your reconciliation script:
- Pulls new USDC transfer events to
0xB0baF...5678since the last sync. - For each transfer, extracts sender, amount, and timestamp.
- Looks up pending invoices for that sender.
- Matches the transfer to invoice #INV-2024-0032 by amount and open status.
- Marks the invoice as paid, storing the tx hash and block timestamp on the invoice record.
- Posts an audit log entry linking the internal invoice ID and the onchain tx hash.
Example Code (Node.js, ethers.js)
const { ethers } = require('ethers');
const USDC_ABI = [
"event Transfer(address indexed from, address indexed to, uint256 value)"
];
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const usdcAddress = '0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; // Mainnet USDC (change for Base/Polygon)
const merchant = '0xB0baF...5678';
async function fetchTransfers(fromBlock, toBlock) {
const contract = new ethers.Contract(usdcAddress, USDC_ABI, provider);
const filter = contract.filters.Transfer(null, merchant);
const events = await contract.queryFilter(filter, fromBlock, toBlock);
return events.map(evt => ({
from: evt.args.from,
to: evt.args.to,
value: evt.args.value,
txHash: evt.transactionHash,
blockNumber: evt.blockNumber,
}));
}
In your reconciliation job, call fetchTransfers for the latest block range, then process each event as described above.
Handling Edge Cases and Failures
Automated reconciliation must handle:
- Duplicate payments: Flag if a customer pays twice for the same invoice.
- Partial payments: Decide whether to mark as partially paid or leave open.
- Overpayments: Credit the excess or refund.
- Late payments: Decide how to handle if received after the due date.
- Mismatched sender address: Flag if payment comes from an unexpected wallet.
- Multiple open invoices: Use amount and metadata to match.
Store all reconciliation attempts and their outcomes for auditability.
Syncing to Accounting and Analytics
Once matched, push payment status updates to your accounting system, data warehouse, or CRM. Store:
- Invoice or subscription ID
- Onchain tx hash
- Confirmed amount
- Payment date (block timestamp)
- Customer wallet address
This supports downstream reporting, revenue recognition, and audit trails.
Building a Stablecoin Ledger for USDC Receipts
Some teams create a dedicated stablecoin ledger, separate from their fiat ledger. Each onchain USDC payment becomes a credit entry, linked to an internal invoice or subscription. Refunds or reversals (where possible) post as debits. Since USDC payments are final onchain, there is no chargeback flow. Marking a payment as reversed is a manual or exceptional process, not an onchain reversal.
Security and Integrity
Never store or request customer private keys or seed phrases. Only process events on your merchant-controlled settlement addresses. Do not assume a payment is final until the transaction is confirmed onchain and visible in your indexer. If using a webhook or third-party API, verify event authenticity and cross-check with the primary chain if in doubt.
How We Handle Automated Onchain Reconciliation at Reclack
At Reclack, we automate USDC payment reconciliation for every invoice, subscription, and usage event. Each onchain payment is linked to its corresponding billable object, with the full transaction hash and confirmation status stored and available through our API and customer portal. We do not hold funds, and every match is auditable. If you want to see how this works in your stack, visit reclack.com or contact us for integration patterns.

