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
reasonfield 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
entriesis 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_atflag. 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
- Double-entry ledger — the balanced-pair structure append-only ledgers preserve
- Idempotency key — how to keep append-only ledgers from double-writing
- Designing fintech ledgers — the studio’s longer field guide