Glossary term

Append-Only Ledger

An append-only ledger never updates or deletes existing entries. Every state change becomes a new insert, preserving full history so reconciliation, audits, and balance reconstruction are deterministic at any point in time.

What append-only means

An append-only ledger only ever inserts new rows. It never updates, never deletes. If a balance needs to change, the change is recorded as a new entry that, combined with the existing entries, produces the desired result. If a transaction needs to be reversed, the reversal is a new pair of double-entry entries — a compensating transaction — not an erasure of the original.

The reason this matters is simple: history is the basis of every regulator-facing answer. Once an entry is mutated or deleted, you have lost the ability to answer questions about what was true at a given moment, and you have introduced an attack surface on your own books.

Why append-only beats reversible

Updateable ledgers feel pragmatic until your first reconciliation discrepancy. Then you discover that a quiet update three months ago has left the books inconsistent, and you have no way to identify it because the previous value is gone. Append-only ledgers make this class of bug structurally impossible — every change leaves a trail.

Append-only also unlocks two features for free: balance reconstruction at any historical timestamp (you replay entries up to that timestamp) and tamper-evident logs (any unauthorized change is detectable because the chain of inserts breaks). For platforms that pass through PCI DSS, SOC 2, or KYC/AML reviews, this is not a feature — it is a baseline expectation.

What “append-only” looks like in production

In Postgres, append-only is enforced with a combination of row-level security, trigger-based prevention of UPDATE and DELETE, and an application-layer service that exposes only insert operations. In event-sourced systems, the event log is naturally append-only and the ledger is a projection over it. In both shapes, the engineering team’s job is to ensure no path through the application can mutate or remove an entry — including database migrations, admin tools, and data backfills.

Common reversal patterns:

  • Compensating transaction: insert a new pair of entries that exactly negates the original. The historical entries remain visible.
  • Partial reversal: insert a smaller compensating pair. Both partial and original remain.
  • Correction with reason code: insert the compensating pair and tag both entries with a reason field so audit reports can group corrections.

Code-shape example

Append-only at the database layer in Postgres:

-- Deny any mutation on entries. Belt-and-braces with the SDK.
CREATE OR REPLACE FUNCTION deny_entry_mutation()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  RAISE EXCEPTION 'entries are append-only; use compensating transactions';
END;
$$;

CREATE TRIGGER deny_entry_update
  BEFORE UPDATE ON entries
  FOR EACH ROW EXECUTE FUNCTION deny_entry_mutation();

CREATE TRIGGER deny_entry_delete
  BEFORE DELETE ON entries
  FOR EACH ROW EXECUTE FUNCTION deny_entry_mutation();

A reversal in the application layer:

// Reverse a payout that bounced from the bank.
await ledger.compensate({
  originalTransactionId: payoutTxId,
  reasonCode: "bank_return_R03",
  description: "Bank returned payout — insufficient routing info",
});
// Internally inserts a paired-entry transaction whose values are the
// negation of every entry in the original transaction.

A balance-at-timestamp query:

SELECT account_id, SUM(amount_minor) AS balance_minor, currency
FROM entries
WHERE created_at <= '2026-03-03 11:42:00+00'
GROUP BY account_id, currency;

This query is what auditors actually ask. Append-only is what makes the answer real.

How we implement it at Dashhold

On every fintech engagement we ship the same hardening:

  • Postgres triggers as the structural backstop. Even if the SDK is misused, the database refuses the write.
  • A compensate() helper in the ledger SDK that takes an original transaction ID + a reason code and atomically inserts the negating pair. Engineers never write reversal logic by hand.
  • Quarterly audit drill. A scheduled job replays every entry from origin to current, computes balances, and compares them against the materialized-view balances. Any drift fails CI.
  • Read-only replicas for analytics. All reporting and BI queries run against replicas. The primary only sees writes and balance reads.
  • Schema migrations are themselves append-only. Adding a column to entries is allowed; renaming or dropping is not. We carry historical column shapes forever and version reads through SDK adapters.

This combination has held up across PCI DSS audits, SOC 2 type II reviews, and one regulator-mandated reconstruction of three months of activity for a partner-bank dispute. The append-only invariant was the only reason the reconstruction was possible.

Common pitfalls

  • Allowing UPDATE on the entries table “just for the migration.” Once the door is open, it stays open. Migrations should run through compensating entries.
  • Soft-deleting with a deleted_at flag. Soft delete is still mutation. Reads filter the flag; audits cannot. Avoid.
  • Storing only the latest balance, not the entries. Without entries, balance reconstruction is impossible. Store both, derive balances from entries.
  • Treating “fix-it” SQL as routine. Every “let me just update this row” in a ledger is a near-miss. Document, gate, log, and monitor any operator that has the privilege to mutate ledger tables — and ideally remove the privilege entirely.
  • Forgetting the retention policy. Append-only forever sounds simple, but the regulator-mandated retention is finite. Build cold-tier archival on day one so the hot tier stays small and queries stay fast.

Where append-only fits

Append-only ledgers are the foundation of every payment platform and every double-entry ledger. They are not optional for regulated work. The studio’s fintech development practice treats append-only as a non-negotiable architectural decision, locked in during the first sprint of a payment-platform engagement, because the cost of retrofitting it later is measured in quarters, not weeks.

See also

Append-Only Ledger FAQ

Common questions

What does append-only mean for a ledger?
Append-only means the ledger only ever inserts new entries. It never updates an existing entry's value, never deletes an entry, never reorders entries. If a balance needs to change, the change is recorded as a new entry that, combined with the existing entries, produces the desired result. The full history is permanent, which is what makes audits and balance-at-timestamp queries possible.
How do I reverse a transaction in an append-only ledger?
You insert a new pair of entries that exactly negates the original transaction. This is called a compensating transaction. The original entries remain visible, the compensating entries are visible, and the audit trail shows both events with their timestamps and reason codes. No mutation of the original record happens, ever.
Doesn't append-only mean the database grows forever?
Yes, and that is the point. Storage is cheap; lost history is expensive. In practice, archival tiers (Postgres partitioning, S3-backed cold storage, BigQuery archive tables) keep query performance fast on the hot tier while preserving older entries for the regulator-required retention period — typically 5 to 7 years for financial records, longer for some jurisdictions.
How is append-only enforced in production?
Three layers. The database layer denies UPDATE and DELETE on the entries table via triggers or row-level security. The application layer's ledger SDK exposes only an insert method. Operational tooling forces compensating entries instead of mutations for any data correction. If any one layer is missing, an admin script eventually bypasses the rule and silently breaks the audit story.
Is append-only the same as event sourcing?
They overlap heavily. Event sourcing treats the event log as the source of truth and derives state from it. An append-only ledger does the same thing for money: every entry is an event, and balances are derived projections. The difference is that ledgers usually maintain materialized balance views for speed, while pure event sourcing recomputes from scratch when needed.

Let's build it together

Building something that depends on this?

The glossary is the short version. The custom analysis happens on the strategy call.