Event-Driven Ledgers vs. CRUD: What Actually Survives a CBN Audit
Most Nigerian fintech teams default to CRUD because it's familiar. But when the CBN asks for a 6-month transaction trail with immutable timestamps, CRUD architectures buckle.
In the past 12 months, we've been asked to audit or rebuild 8 fintech ledgers in Nigeria. In 6 of those 8, the core architecture was a CRUD table: transactions with an amount, a direction, a status, and a timestamp. It worked. Until it didn't. Until the CBN asked a question the architecture couldn't answer.
The Question That Breaks CRUD
The CBN doesn't ask 'what is the current balance?' They ask: 'Show me every state change for merchant X between March 1 and March 31, 2026, with the exact timestamp of each transition, the actor who initiated it, and the system state before and after each change.'
A CRUD ledger can tell you the current balance. It can tell you the last update timestamp. It cannot tell you the state before the last update, because that state was overwritten. The UPDATE query destroyed the previous row. It's gone.
This isn't a theoretical problem. We've seen a fintech company lose a 3-week CBN audit because they couldn't produce the transaction trail for a disputed merchant. The data was there in their logs, but not in their database. The CBN doesn't accept logs. They want the system of record.
“CRUD gives you the present. Event sourcing gives you the past, the present, and the ability to prove both. The CBN wants the proof.”
How Event Sourcing Actually Works
Instead of storing 'balance = ₦50,000,' you store a sequence of events: 'DEPOSIT ₦20,000 at 10:02:33 by user_123', 'WITHDRAWAL ₦5,000 at 14:15:07 by user_456', 'FEE ₦500 at 14:15:07 by system'. The balance is a computed value — the sum of all events up to now.
The critical property: events are append-only. You never update an event. You never delete an event. If a withdrawal needs to be reversed, you add a 'REVERSAL' event. The original event remains, with its original timestamp, its original actor, its original context.
This means you can answer any CBN question: 'Show me every state change between March 1 and March 31' is a simple SELECT with a date range. 'What was the balance at 2:15 PM on March 15?' is a SUM of all events before that timestamp. 'Who initiated the ₦5,000 withdrawal?' is in the event payload.
// Event-Sourced Ledger — Production Pattern
// Handles 2M+ transactions/day
interface LedgerEvent {
eventId: string; // UUID, immutable
merchantId: string;
type: 'DEPOSIT' | 'WITHDRAWAL' | 'FEE' | 'REVERSAL' | 'ADJUSTMENT';
amount: bigint; // in kobo, always positive
direction: 'IN' | 'OUT';
actorId: string; // user_id, system, or cban_ref
actorType: 'USER' | 'SYSTEM' | 'REGULATOR';
timestamp: Date; // immutable, server-side
previousBalance: bigint; // the balance BEFORE this event
newBalance: bigint; // the balance AFTER this event
metadata: Record<string, unknown>; // bank_ref, cban_ref, etc.
}
// The ledger is a table of events. No UPDATE. No DELETE.
// Balance = SUM of all events for a merchant, ordered by timestamp.
export function getBalanceAt(
events: LedgerEvent[],
merchantId: string,
asOf: Date
): bigint {
return events
.filter(e => e.merchantId === merchantId && e.timestamp <= asOf)
.reduce((sum, e) => {
return e.direction === 'IN' ? sum + e.amount : sum - e.amount;
}, 0n);
}
// CBN Audit Query: "Show all state changes for merchant X in March 2026"
export function cbanAuditTrail(
events: LedgerEvent[],
merchantId: string,
from: Date,
to: Date
): LedgerEvent[] {
return events
.filter(e =>
e.merchantId === merchantId &&
e.timestamp >= from &&
e.timestamp <= to
)
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
}
// Every event carries its before/after balance.
// This is the key: you can prove the arithmetic.
// previousBalance + amount (or - amount) === newBalance
// If that equation fails, you have a data integrity issue.
// The CBN can verify this independently.The Practical Differences
Storage cost: Event sourcing stores more data. A CRUD ledger stores one row per merchant. An event ledger stores one row per transaction. For a merchant with 10,000 transactions, that's 10,000 rows vs. 1. But modern databases handle this trivially, and the storage cost is negligible compared to the cost of a failed audit.
Query complexity: CRUD queries are simpler. 'SELECT balance FROM merchants WHERE id = X' is one row. Event sourcing requires a SUM over potentially thousands of rows. But with proper indexing (merchant_id, timestamp), this query runs in under 5ms even with millions of events.
Debugging: This is where event sourcing wins by a mile. When a customer disputes a balance, you don't have to guess what happened. You replay the events. You see exactly when the balance changed, by whom, and what the state was before and after. No guessing. No 'I think it was the fee that caused it.'
When CRUD Is Actually Fine
Let's be honest: not every system needs event sourcing. If you're building a simple internal tool for a 10-person company that tracks expenses, a CRUD table is fine. The CBN isn't auditing your expense tracker.
But if you're handling money — real money, customer money, regulated money — and you expect to survive a regulatory audit, CRUD is a risk you're taking. And in Nigeria, that risk has a very specific name: it's the name of the company that lost its licence last year because they couldn't produce their transaction trail.
Technical Summary
We've built event-sourced ledgers for three fintech clients in the past 18 months. In each case, the CBN audit was clean. In each case, the audit trail was produced in under 5 minutes. In each case, the client told us the same thing: 'We never knew we were this close to a problem until you showed us the event trail.' CRUD gets you to launch. Event sourcing gets you to survive the audit after launch. For regulated fintech in Nigeria, that's not optional. That's the architecture.
Sign up to receive a weekly recap from Emicraft
Deep-dives on software architecture, design systems, and scaling senior engineering squads. No marketing fluff — only production insights.
Emicraft Engineering Team
Fintech Systems
Software engineer building resilient web frontends, offline-capable PWAs, AI compilers, and financial transaction engines at Emicraft.