Softech Blog
Digital Assets & Financial Infrastructure

How to Design an Internal Ledger for Stablecoin Payments

Why blockchain balances and provider dashboards cannot replace a product-owned ledger — and how to model payment intents, immutable entries, credit, refunds and treasury separation.

5 min read
How to Design an Internal Ledger for Stablecoin Payments
Executive summary

The most important points from this article

A product-owned ledger records the business meaning of value movement. It should not mirror wallet balances; it should record accepted payment events, credits, debits, fees, refunds and adjustments with immutable references to the external provider or blockchain evidence.

Key takeaways
  • Store business reason and external evidence on every ledger entry.
  • Separate available product balance from wallet/treasury balances.
  • Use immutable entries and compensating events instead of destructive updates.
  • Enforce idempotency at the ledger boundary.
  • Reconcile ledger totals with external rails on a schedule.
Key insights

Key observations and insights

The key observations summarizing the experience, decisions and outcomes described in the article.

Wallet balance answers “how many tokens are here?”; the ledger answers “why does the product owe or credit this value?”.
Provider state is evidence; ledger state is the product’s accounting interpretation.
Treasury movement must not reverse a valid customer payment.
Every external event needs an idempotent ledger effect.

Blockchain balance is not product balance

A wallet can hold 10,000 USDC and still tell you almost nothing about your product. It does not know whether those funds belong to ten customers, one invoice, a treasury transfer, a refund return or an operational top-up.

Wallet balance answers “where are assets?”. The internal ledger answers “why does the product recognise this value?”.

What the internal ledger is for

The ledger creates a durable business interpretation of external payment evidence. A provider order or blockchain transaction proves that something happened outside your database. The ledger records what that event means inside your product.

Examples of ledger events:

  • PAYMENT_ACCEPTED
  • CUSTOMER_CREDITED
  • PAYMENT_FEE_RECORDED
  • REFUND_INITIATED
  • REFUND_COMPLETED
  • MANUAL_ADJUSTMENT
  • TREASURY_TRANSFER_RECORDED

Do not model the ledger as one mutable balance field

A mutable user.balance += amount operation loses the reason, evidence and history behind the change. Prefer an append-only set of entries and derive balances from those entries or from a carefully maintained projection.

PaymentIntent

The expected business payment: customer, invoice/order, amount, currency, expiry and allowed rail.

ExternalPayment

The evidence object: provider order ID or blockchain transaction hash, network, asset, amount, timestamps and external state.

LedgerEntry

An immutable business value event with debit/credit direction, amount, currency/unit, reason code, reference to PaymentIntent and ExternalPayment, idempotency key and created timestamp.

BalanceProjection

A derived view used for performance. The projection can be rebuilt from ledger entries; it should not be the only evidence.

SettlementRecord

A separate object representing provider settlement, withdrawal or treasury movement. A customer payment may be valid before settlement completes.

Idempotency belongs at the ledger boundary

Duplicate callbacks and repeated chain observations are normal. The same external event must never create the same economic effect twice. Store a unique key such as provider:event:id or chain:txHash:logIndex:businessAction and enforce uniqueness transactionally.

Credit and settlement are different facts

Consider a managed provider flow. The provider can confirm the customer payment and your product can credit the order. The provider’s EUR settlement may occur later. If settlement is delayed, the customer payment does not become invalid. Your model should therefore separate paymentAcceptanceState from settlementState.

The same rule applies to custom wallets. A valid deposit can credit the customer while a later treasury sweep fails. The sweep is an operational movement, not a reason to reverse the original payment.

Refunds should create compensating entries

A refund does not delete the original credit. Create a new refund lifecycle and new ledger entries. This preserves a complete explanation of gross payment, fees, partial refunds and net retained value.

Multi-currency and stablecoin units

Decide which unit each ledger represents. For B2B invoicing you may keep a commercial ledger in EUR and store USDC payment evidence separately. For a native stablecoin product you may operate a USDC-denominated product ledger. Do not mix units in one balance without explicit conversion entries and rate evidence.

Suggested ledger entry shape

FieldPurpose
entryIdImmutable identifier
accountIdCustomer, merchant, clearing or fee account
directionDebit / credit
amount + unitValue represented by this ledger
reasonCodePAYMENT, REFUND, FEE, ADJUSTMENT…
paymentIntentIdBusiness context
externalReferenceProvider order/event or chain tx
idempotencyKeyPrevents duplicate economic effects
createdAtAudit ordering

Reconciliation is the proof that the ledger is correct

An internal ledger without external reconciliation can still drift. Scheduled jobs should compare ledger totals and entries against provider or chain evidence. The process is described in Stablecoin Payment Reconciliation.

Provider and custom wallet differences

With CoinGate, provider orders and ledger transactions become external evidence. With custom wallets, blockchain transactions and chain observations become external evidence. The internal ledger architecture remains conceptually the same.

What the ledger should not do

  • It should not store private keys.
  • It should not infer product credit directly from raw wallet balance.
  • It should not overwrite history to “fix” mistakes.
  • It should not collapse payment, treasury and settlement into one status.
  • It should not pretend to replace statutory accounting.

Ledger invariants

A production ledger should enforce invariants in code and at the database level where possible. Examples: an external event can create a given economic effect only once; entries are immutable after posting; debits and credits use a known unit; adjustments reference the entry or event being corrected; and a payment cannot be credited before the acceptance policy is satisfied.

Account structure

Even if you do not implement a full accounting general ledger, explicit logical accounts make the model clearer. Typical accounts can include customer available balance, merchant clearing, provider clearing, fees, refunds and treasury. Moving value between logical accounts is easier to audit than updating several unrelated balance fields.

Concurrency and transactions

Payments are concurrent systems. Two callbacks can arrive at the same time, or a callback can race with a reconciliation job. Ledger posting should happen inside a database transaction with uniqueness constraints on idempotency keys. If a projection is updated, update it in the same transaction or through a replayable event pipeline.

Audit queries the ledger should answer

  • Why was this customer credited?
  • Which external transaction caused this entry?
  • Has this payment been refunded, partially refunded or adjusted?
  • Which entries contributed to the current product balance?
  • Has the accepted payment been reconciled with settlement?

Conclusion

The internal ledger is the boundary that lets a product use multiple payment rails without losing business consistency. Build it once around explicit value events and the system can later accept CoinGate, direct USDC deposits, cards or bank transfers while keeping the same core business model.

Solution framework

Key elements and relationships

Stablecoin Product Ledger

Separate evidence, business acceptance and money movement.

Layer 1
Payment intent

What the business expected.

Layer 2
External evidence

Provider order or blockchain transaction.

Layer 3
Ledger event

Why value was credited/debited.

Layer 4
Product balance

Derived business balance or entitlement.

Layer 5
Treasury state

Where assets physically reside; separate from product balance.

Evidence and context

Information supporting the analysis

USDC is described by Circle as an e-money token under MiCA for the EEA.

MiCA establishes an EU framework for crypto-assets and related services not already covered by other EU financial-services legislation.

CoinGate exposes a ledger transactions API for tracking credits and debits associated with its account, which illustrates the operational separation between orders and ledger movements.

Circle Wallets distinguishes wallet custody/control models from application business logic, reinforcing the need to model product state separately from wallet infrastructure.

FAQ

Is an internal ledger the same as accounting software?
No. The product ledger is an operational source of truth for product value events. It can feed accounting systems, but it does not replace statutory accounting or tax records.
Can I just use wallet balances?
No. Wallet balances do not identify the business reason for a transfer, which customer should be credited, whether a payment was accepted, or whether a later treasury movement occurred.
Should ledger entries be editable?
Prefer immutable entries with compensating or adjustment entries. This preserves an audit trail and makes reconciliation deterministic.
Continue reading

Related articles

Articles that expand the topic and add further practical context.

Author

Matt Dudzicz · Softech.app

Founder

Founder of Softech.app, focused on product engineering, digital asset infrastructure, custom software and AI-native business systems.

LinkedIn
Next step
Designing stablecoin payments as part of the product?
We compare a managed gateway with custom on-chain architecture and design payment state, ledger and reconciliation.