Metered Stablecoin Billing: Usage, Idempotency, and Collection
Learn how to build usage-based billing for stablecoin payments: metering usage, using idempotency keys, and collecting USDC at period close. Includes code and a real example.

Usage-based billing lets you charge customers for what they use, not just a fixed amount each month. In stablecoins, this means collecting USDC at the end of a metering period, based on measured consumption. You need to meter usage, store it reliably, generate a bill, and collect payment from the customer’s wallet. This guide covers the mechanics: how to track usage, avoid double-charging with idempotency keys, and settle the right amount at the right time.
Understanding Metered Billing in Crypto
Metered billing means you charge for usage: API calls, storage, compute, or another unit. Each billing cycle, you total up the usage for an account. At the end of the period, you bill the customer for exactly what they used.
In the context of crypto payments, especially with USDC on Base, Ethereum, and Polygon, you have a few extra requirements:
- Usage data must be reliable and tamper-resistant.
- The final bill must match what the customer authorized.
- Every transaction must be idempotent, so you never double-charge.
- Payment moves from the customer’s wallet directly to your settlement address. There is no chargeback.
Metering Usage: What and How to Track
Billing starts with measuring what the customer has used. This usually means writing usage records into a database, keyed by customer, resource, and timestamp. You want to track each usage event, but you also want to be able to aggregate it by billing period.
Example: API Usage Tracking
Suppose you are charging $0.05 per API call. You need to record each call, or batch them as a daily tally. Common approaches:
- Event-level metering: Store each API call as a row in a table, including customer ID, timestamp, and units used.
- Aggregate metering: For higher-volume systems, increment counters for each customer and resource, then snapshot these at the end of the period.
Sample Usage Table Schema
CREATE TABLE usage_events (
id SERIAL PRIMARY KEY,
customer_id UUID NOT NULL,
resource VARCHAR NOT NULL,
units INT NOT NULL,
occurred_at TIMESTAMP NOT NULL,
idempotency_key VARCHAR UNIQUE NOT NULL
);
Each row records one usage event. The idempotency_key ensures you do not double-insert events from retries or race conditions.
Idempotency Keys: Preventing Double-Charges
Stablecoin transfers are final. If you run the same billing code twice, you could double-charge a customer. Idempotency keys prevent this. You generate a unique key for each billing action, whether it’s a usage event or a full invoice. The system checks for an existing record before creating a new one.
Where to Use Idempotency Keys
- Usage events: Ensure each event is counted once.
- Invoice generation: Prevent multiple invoices for the same period.
- Payment requests: Make sure that only one payment is collected per period, even if a network outage causes retries.
Example: Handling Usage Events With Idempotency Key
def record_usage(customer_id, resource, units, idempotency_key):
try:
db.insert('usage_events', {
'customer_id': customer_id,
'resource': resource,
'units': units,
'occurred_at': datetime.utcnow(),
'idempotency_key': idempotency_key
})
except UniqueViolation:
# Already recorded, safe to ignore
pass
Here, attempting to insert a duplicate usage event with the same idempotency key will fail harmlessly. The caller can retry safely.
Generating the Usage-Based Invoice
At the close of the billing period, you need to:
- Aggregate usage for each customer.
- Calculate the amount owed.
- Present the bill to the customer for authorization (if not pre-authorized).
- Initiate an onchain USDC payment from the customer’s wallet to your settlement address.
Example: Calculating a Bill
Suppose the customer made 200 API calls this month at $0.05 each. The total is $10.00.
SELECT SUM(units) as total_calls
FROM usage_events
WHERE customer_id = :customer_id
AND occurred_at >= :period_start
AND occurred_at < :period_end;
total_calls = 200
rate_per_call = 0.05
total_due = total_calls * rate_per_call # $10.00
Presenting the Bill
For onchain collection, you have two main flows:
- Pre-authorized spend: Customer has signed a bounded approval (e.g., up to $50 per month). You can pull up to the cap without further signatures.
- Invoice/checkout: Customer reviews and signs a one-time payment for the owed amount.
If you are using a pre-authorized bounded approval, always check that the charge is within the customer’s cap. Never attempt to pull more than what they have authorized.
Collecting Payment at Period Close
When it is time to collect, you submit a transaction that moves USDC from the customer’s wallet to your settlement address. Reclack never holds funds. The customer either signs a new transaction or has previously signed an approval with a cap, and the smart contract executes the transfer.
Handling Settlement and Failure Cases
- If the transaction is confirmed, the payment is final.
- If the transaction fails (insufficient allowance or balance), you must notify the customer and possibly retry after they top up their wallet or increase their allowance.
- Never attempt to settle more than once for the same period and customer. Use an idempotency key tied to the billing cycle and customer ID.
Example: Settlement Logic With Idempotency
def settle_usage_bill(customer_id, period, idempotency_key, amount_due, usdc_contract, merchant_address):
if payment_already_settled(customer_id, period):
return 'Already settled'
try:
# Call Reclack API or your own contract logic to transfer USDC
tx_hash = usdc_contract.transfer_from(
from_address=customer_id,
to_address=merchant_address,
amount=amount_due,
idempotency_key=idempotency_key
)
record_settlement(customer_id, period, tx_hash)
return 'Settled', tx_hash
except InsufficientAllowance:
notify_customer_to_increase_cap(customer_id)
return 'Failed: insufficient allowance'
except InsufficientBalance:
notify_customer_to_top_up(customer_id)
return 'Failed: insufficient balance'
Use a settlement log keyed by customer and period. This ensures each period is settled only once, even if you retry after a failure.
Worked Example: API Service Charging Per Use
Suppose you run an API service metered by API calls. Pricing is $0.05 per call. Alice uses your API 340 times in April. She pre-authorized up to $20 per month. You aggregate her usage at month-end:
- Calls: 340
- Rate: $0.05
- Owed: $17.00
- Authorization cap: $20
You check her usage is under the cap. You call the onchain contract to move $17.00 USDC from Alice’s wallet to your settlement address. The contract uses the idempotency key alice-2024-04 for the April period. Transaction confirms on Base. Alice’s bill for April is settled. If your code or the network retries, the key prevents a second charge.
Best Practices for Metered Stablecoin Billing
- Use idempotency keys everywhere you record or collect.
- Always check usage against the customer’s authorization cap.
- Log every usage event and settlement, tied to period and customer.
- Surface errors to customers clearly: balance, allowance, or contract failure.
- Never attempt to reverse a settled transaction. If you need to refund, send a new payment.
How We Handle This at Reclack
At Reclack, we built usage-based billing for USDC with these principles. We use idempotency keys at every step: metering, invoicing, and collecting. All approvals are bounded, with the cap visible to the customer before they sign. We never hold funds or ask for a seed phrase. When a period closes, we collect from the customer’s wallet to your settlement address, with clear audit logs for every event and transaction. You can read more about how this works at reclack.com.

