Fintech technology
Businesses Fintech June 28, 2026 • 8 min read

Event Sourcing Explained: Why Your Audit Log Is Lying to You

For: A CTO or lead engineer at a fintech SMB — lending, payments, or accounting SaaS — whose compliance audit just exposed gaps between what the database shows today and what actually happened to a user's account six months ago, and who is now evaluating whether to bolt on better logging or rethink how state is stored

If your audit table and your production database disagree about what a user's balance was on March 14th, the audit table is probably wrong — and the database has overwritten the evidence. Event sourcing fixes this not by adding better logging, but by inverting the storage model: every change is stored as an immutable event, and the "current state" of any record is recomputed by replaying those events. The log is the system. The database row is just a cached opinion.

This post explains event sourcing for engineering leaders at fintech SMBs — lending platforms, payment processors, accounting SaaS — who just got burned by a compliance request and are wondering whether to bolt on more logging or rethink storage. We'll cover the problem, an analogy, a minimal worked example, the gotchas nobody mentions in conference talks, and when it's the wrong tool.

The problem: your database is a liar by design

A standard CRUD system stores the latest state of each row. When a loan amount changes from ₹50,000 to ₹45,000, the UPDATE statement overwrites the old value. Maybe you have a trigger that writes to loan_audit. Maybe you have CDC streaming to a warehouse. Either way, you now have two sources of truth: the live row and the audit trail. They drift. They always drift.

The drift comes from places you don't expect:

When a regulator asks "why did this borrower's credit limit drop on this date," you can't answer with confidence. You have a log. You don't have the log. That's the difference between an audit log and an event-driven architecture where the audit log is the database.

The analogy: chess notation, not chess board

Imagine you walk into a chess game halfway through. You can photograph the board. That's CRUD — current state, no history. If someone asks "was the white bishop ever on e5?", you have no idea.

Now imagine instead of photographing the board, you have the move list: 1. e4 e5 2. Nf3 Nc6 3. Bb5... You can reconstruct the board at any move. You can answer any historical question. You can replay from move 1 to verify the current position is correct. You can spot illegal moves. You can branch: "what if move 12 had been different?"

Event sourcing is the move list. CRUD is the photo. For a fintech system where every state change is potentially a regulated event, you want the move list.

A minimal worked example: a lending account

Take a loan account. In CRUD, you have a row:

loans(id, borrower_id, principal, interest_rate, status, updated_at)

In event sourcing, you have an append-only event stream for that loan:

LoanApplied        { loan_id, borrower_id, requested_amount: 50000, ts: 2024-01-10 }
KycVerified        { loan_id, kyc_provider, ts: 2024-01-11 }
LoanApproved       { loan_id, principal: 50000, rate: 14.5, ts: 2024-01-12 }
Disbursed          { loan_id, amount: 50000, ts: 2024-01-12 }
RepaymentReceived  { loan_id, amount: 5000, ts: 2024-02-12 }
PrincipalAdjusted  { loan_id, new_principal: 45000, reason: 'partial_prepayment', ts: 2024-02-12 }
RateRevised        { loan_id, new_rate: 13.0, reason: 'rbi_circular_2024_03', ts: 2024-03-15 }

To get the current state, you start with an empty loan and fold the events left-to-right. To get the state on February 20th, you fold only events with ts <= 2024-02-20. To answer "why is the rate 13.0?", you grep for the event that set it and read the reason.

The current-state table still exists — but it's a projection, a derived cache. If it gets corrupted, you rebuild it by replaying events. If you discover a bug in how interest was computed three months ago, you fix the projection logic and replay. The events themselves are never edited. They're facts that happened.

Event sourcing vs CRUD: what actually changes

AspectCRUDEvent Sourcing
Source of truthCurrent rowEvent stream
HistoryLost on UPDATEImmutable, by construction
Audit logSeparate, driftsThe log is the data
Reconstruct past stateHope your backups workReplay to any timestamp
Fix historical bugManual SQL surgeryFix projection, replay
Write complexityLowHigher — schema decisions matter
Read complexitySELECT *Read projection (denormalized)
StorageCompactGrows forever

The gotchas nobody warns you about

1. Event schema versioning is the hardest part

Your LoanApproved event from 2022 has fields your 2025 code doesn't expect, and vice versa. You need an upcasting strategy (transform old events to new shape on read) or a tolerant reader pattern. Get this wrong and replay breaks. Most teams underestimate this until two years in.

2. GDPR and the right to be forgotten

Immutable events meet a law that demands deletion. The standard answer is crypto-shredding: encrypt PII per-subject and throw away the key on deletion request. The event remains; the payload becomes unreadable. Plan this on day one, not day 800.

3. Eventually consistent reads

Projections lag the write stream. A user submits a payment, hits refresh, and doesn't see it yet. You need read-your-writes patterns or synchronous projections for critical paths. Fine for analytics, painful for "did my transfer go through."

4. Replays get slow

By year three you have millions of events per aggregate. Full replays take hours. You need snapshots — periodic state checkpoints — so a rebuild starts from the latest snapshot and only replays events after it.

5. It's not a logging library

You can't sprinkle event sourcing on a few tables. It's a domain modeling commitment. You have to think in terms of business events (LoanApproved, RateRevised) not technical mutations (UPDATE loans SET rate=...). Teams that skip the modeling work end up with "CrudUpdated" events and all the cost of event sourcing with none of the benefit.

When to use it (and when not to)

Use event sourcing when:

Don't use it when:

A pragmatic middle path: use event sourcing for the regulated core (loan ledger, payment ledger, KYC decisions) and plain CRUD for everything else (user preferences, support tickets, admin tooling). Most production fintech systems we've seen at accounting SaaS and lending platforms end up here. Pure event sourcing across the whole system is rare and usually unnecessary.

The shift in mindset

The hardest part of adopting event sourcing isn't the tooling — Kafka, EventStoreDB, Axon, or a plain Postgres table with an append-only constraint all work. The hard part is convincing your team to stop thinking "what is the current state and how do I change it" and start thinking "what just happened in the business, and how do I record it."

Once that flip happens, the audit log question disappears. You don't have an audit log. You have a system that is an audit log, which happens to also serve current state on the side. The regulator's question stops being terrifying and starts being a SELECT.

Frequently Asked Questions

Is event sourcing the same as using Kafka?

No. Kafka is a log-based message broker and can be used as event store, but event sourcing is a domain modeling pattern, not a piece of infrastructure. You can do event sourcing with a single Postgres table that has an append-only constraint, and you can use Kafka without doing event sourcing at all. Conflating the two leads to teams adopting Kafka and thinking they got auditability for free — they didn't.

Can I retrofit event sourcing onto an existing CRUD fintech system?

Partially, yes. The common approach is to identify the regulated aggregates (loan ledger, payment ledger) and migrate just those to event sourcing while leaving the rest as CRUD. You bootstrap the event stream from current state plus whatever historical audit data you have, accepting that pre-migration history will be lower fidelity. Full rewrites rarely succeed; incremental carve-outs do.

How does event sourcing handle GDPR or DPDP deletion requests?

The standard pattern is crypto-shredding: encrypt personal data fields in events using a per-subject key, and on a deletion request, destroy the key. The event record remains for system integrity, but the PII payload becomes permanently unreadable. This needs to be designed in from the start — adding it later is painful.

What's the performance cost of rebuilding state from events?

For a single aggregate with thousands of events, rebuilding is sub-millisecond. For aggregates with millions of events, you use snapshots — periodic state checkpoints stored alongside the stream, so rebuilds start from the latest snapshot and only replay subsequent events. Read performance for normal operations is identical to CRUD because you query the projection, not the raw event stream.

How do I decide if my system actually needs this?

Ask: when a regulator, customer, or internal auditor asks "what was the state on date X and why," can you answer with confidence today? If the answer involves "let me check the audit table and hope it's complete," you have a gap that event sourcing closes. If your domain doesn't generate those questions, you probably don't need it. For a tailored assessment of whether your ledger architecture justifies the shift, talk to CodeNicely.

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