Fintech technology
Businesses Fintech August 1, 2026 • 8 min read

Event Sourcing Explained: Stop Losing Your Audit Trail

For: A COO or head of operations at a 50–200-person lending or payments company who just failed a compliance audit because the engineering team cannot reconstruct exactly how a loan status or account balance reached its current value — only the final state was stored, not the transitions

If your database only stores the current balance, current loan status, or current KYC state — and overwrites the previous value on every update — you cannot reconstruct history, and no amount of logging will retrofit a trustworthy audit trail. Event sourcing fixes this by inverting the model: instead of storing the current state, you store every state change as an immutable event, and the current state is derived by replaying those events. The audit trail is a byproduct. The real win is that your event log becomes the single source of truth, and any read model — current balance, month-end statement, a fraud detection view you haven't built yet — can be rebuilt from it.

This is the concept in one paragraph. The rest of this post explains why it matters for fintech, walks through a minimal worked example, and is honest about where event sourcing hurts.

The problem: CRUD forgets

Most systems are built on CRUD — Create, Read, Update, Delete. A loan record has a status column. When the loan moves from pending to approved, the code runs UPDATE loans SET status='approved' WHERE id=.... The previous value is gone. If someone later asks "who approved this loan, at what time, based on what credit score, and was it briefly flagged as fraud in between?" — you're guessing, or you're stitching together partial answers from application logs that were never designed to be authoritative.

For a payments or lending business, this is the failure mode that shows up during a regulator audit or a customer dispute. A borrower claims their balance is wrong. You can show them what it is. You cannot show them, event by event, how it got there. That's the gap between an audit log database pattern that engineers built for debugging and an actual audit trail a regulator will accept.

The analogy: bank ledger vs. balance sheet

A traditional CRUD database is like a whiteboard with your bank balance written on it. Every deposit or withdrawal, you erase and rewrite. At any moment you know the balance. You have no idea how you got there.

An event-sourced system is like an actual bank ledger. You never erase. You append: "+$500 deposit on Tuesday", "-$120 withdrawal Wednesday", "+$1,000 transfer in Thursday." The balance is not stored — it's computed by summing the ledger. If you suspect an entry was calculated wrong, you don't tamper with the balance. You post a correcting entry, or you re-run the sum with fixed logic.

Every accountant since the 15th century has worked this way. Software mostly doesn't. Event sourcing is the software version of double-entry thinking.

A minimal worked example: a loan account

Say you're running a lending product. In a CRUD world, your loans table has columns like id, principal, status, outstanding_balance, updated_at.

In an event-sourced world, you have an events table that is append-only. For one loan, it might look like this:

event_idloan_idtypepayloadtimestamp
1L-947LoanApplied{principal: 50000, tenor: 12}10:02:14
2L-947KYCVerified{score: 720, provider: 'X'}10:04:01
3L-947LoanApproved{approver: 'system', rate: 14.5}10:04:03
4L-947Disbursed{amount: 50000, to_account: '...'}10:07:22
5L-947RepaymentReceived{amount: 4500}Day 30
6L-947RepaymentReceived{amount: 4500}Day 60

To answer "what is the current outstanding balance?" you replay events 1 through 6 through a function that knows how to fold them into a balance. That's your read model or projection. You can (and usually do) cache the projection in a regular table for query performance, but the cache is disposable. The events are the truth.

Now the interesting part. Suppose in month three you discover your interest calculation was wrong for a class of loans — it compounded daily when it should have compounded monthly. In a CRUD system, you're writing migration scripts that surgically patch balances in production. Risky, hard to audit, easy to get wrong.

In an event-sourced system, you fix the projection logic and replay the events into a new balance table. The event history is untouched — nobody applied for a loan differently, nobody paid differently. Only your interpretation was wrong, and you corrected the interpretation. Regulators love this because it's transparent: here are the raw facts, here is the code that turned facts into balances, here is the diff.

Event sourcing vs CRUD: when the tradeoff is worth it

The event sourcing vs CRUD decision is not aesthetic. It's a tradeoff.

Event sourcing is worth it when:

Stick with CRUD when:

The gotchas (this is where teams get hurt)

Event sourcing is not free. Here's what nobody tells you until you're six months in:

1. Schema evolution is hard

Events are immutable. If you named a field customer_id in 2022 and now call it borrower_id, the old events still say customer_id. You need an upcasting layer that translates old event versions into what current code expects. Plan for this from day one.

2. Eventual consistency is real

The projection (read model) usually lags the event write by milliseconds to seconds. If your UI writes an event and then immediately reads the projection, you'll get stale data. Teams build workarounds — read-your-own-writes, synchronous projections for hot paths — but the mental model shift is significant.

3. Snapshots become necessary at scale

Replaying 400,000 events to compute one account balance is slow. You periodically snapshot the current state and only replay events since the last snapshot. Snapshots are a cache invalidation problem waiting to bite you.

4. PII and the right to be forgotten

GDPR and similar rules give users the right to have personal data erased. But your events are supposed to be immutable. The usual solution is crypto-shredding: store PII encrypted inside events with per-user keys, and delete the key when the user requests erasure. The event stays, the data becomes unreadable. Design this in early or you will regret it.

5. Not every event needs to be sourced

You do not need to event-source your entire system. Most successful implementations event-source the core financial or regulated aggregates — accounts, loans, transactions — and leave everything else (user profiles, marketing preferences, feature flags) as regular CRUD.

How this connects to building an audit trail

If someone asks you how to build an audit trail in software, the usual answer is "add a history table with triggers" or "log every mutation to a separate store." Both work as forensic tools. Neither is the source of truth. Under audit pressure, discrepancies between the primary tables and the audit tables become their own problem — which one is correct?

Event sourcing collapses that gap. There is one log. The current state is a function of the log. If the state and the log disagree, the state is wrong by definition, and you rebuild it. That's a much stronger position to defend to a regulator than "we think our audit table captured everything."

This is particularly relevant for lending platforms managing KYC and credit decisions, where every state change on a loan or user profile has downstream compliance implications, and for accounting and invoicing systems where ledger integrity is the entire product.

What to do on Monday

If you just failed an audit, don't rewrite your whole system. Do this instead:

  1. Identify the two or three aggregates (loan, account, transaction) where you needed history and didn't have it.
  2. Start writing events for those aggregates in parallel with your existing CRUD writes — dual-write for a few weeks.
  3. Build one projection off the event stream and diff it against your current state table. When they agree consistently, you know the event stream is trustworthy.
  4. Cut reads over to the projection. Retire the direct writes to the state table.

This is a months-long migration, not a weekend. But you don't need a big-bang rewrite, and you don't need to event-source everything. You need it where regulators and customers ask hard questions. Teams working through this kind of migration often bring in outside help for the initial architecture — legacy modernization work around ledger and state systems is a specific skill, and getting the event schema wrong early is expensive to fix later.

Frequently Asked Questions

Is event sourcing the same as an audit log?

No. An audit log is a secondary record written alongside your primary state. Event sourcing makes the log the primary record — current state is derived from it. This eliminates the possibility of the audit log disagreeing with reality, which is the failure mode that trips up compliance reviews.

Do we need Kafka or a specialized event store?

Not to start. Many production event-sourced systems run on Postgres with an append-only events table plus outbox pattern for downstream propagation. Dedicated event stores (EventStoreDB, Kafka with compaction, AWS's offerings) help at scale or when you have many consumers, but they are not prerequisites.

Can we add event sourcing to an existing system without a full rewrite?

Yes, and this is usually how it's done. You pick the one or two aggregates where history matters most, dual-write events alongside your existing CRUD updates, verify the event stream can reproduce current state, then migrate reads. The rest of the system stays as-is.

How do we handle GDPR "right to be forgotten" with immutable events?

The standard pattern is crypto-shredding: encrypt personal data inside events with a per-subject key, store the key separately, and delete the key when erasure is requested. The event remains for audit integrity, but the personal data inside it becomes permanently unreadable.

What's the timeline and cost to migrate a lending platform to event sourcing?

It depends heavily on the number of aggregates, current data volume, regulatory scope, and team familiarity with the pattern. There is no useful generic answer — contact CodeNicely for a personalized assessment based on your specific system.

Found this useful? CodeNicely publishes engineering and product playbooks weekly. Browse the archive or tell us what you're building.