
"Bank logs" means different things to different people. For a developer it means a structured event stream. For a compliance officer it means an auditable record of every transaction. For an accountant it means a reconcilable ledger. For all of them, the practical question is the same: how do you get a complete, reliable record of what happened in a bank account over time?
This guide covers what bank logs contain, what South African law requires you to keep, and how to capture them without depending on someone manually downloading a statement every month.
What bank transaction logs contain
A complete bank transaction log has at minimum:
- Date — when the transaction was posted to the account
- Description — the narrative the bank assigns (merchant name, reference, counterparty)
- Amount — debit or credit, in ZAR
- Running balance — the account balance after each transaction
- Reference number — a unique identifier assigned by the bank
For compliance and reconciliation purposes, the reference number is critical. It is the immutable link between your internal records and the bank's records. Without it, disputes are difficult to resolve.
What South African law requires
The Financial Intelligence Centre Act (FICA) requires covered entities — banks, accountants, estate agents, lawyers, and anyone else designated under the Act — to retain transaction records for at least five years from the date of the last transaction.
Records must be:
- Readily available for examination by the Financial Intelligence Centre or SARS
- Sufficient to reconstruct the transaction — amount, parties, date, type
- Kept in a format that can be produced in evidence if required
South Africa's compliance environment is tightening in 2026. The FIC, FSCA, and Prudential Authority are all in active enforcement mode. "We had it in an email somewhere" is not an acceptable audit trail.
Why manual exports fail as a logging strategy
The most common approach for smaller businesses is periodic manual exports: someone logs into internet banking, downloads the last month as a CSV or PDF, and files it.
This breaks in several ways:
Gaps in the record. If the export happens monthly, any transactions that fall near month boundaries — especially anything that posts or reverses after the download — may be missed or duplicated.
Format inconsistency. FNB, Standard Bank, ABSA, and Nedbank all produce slightly different CSV column layouts. A CSV from 2023 may not import cleanly into the same software in 2026 after a bank changes their export format.
No verification. A manually downloaded file has no cryptographic signature and no automated validation. There is no way to prove it has not been edited.
Human dependency. If the person who does the downloads leaves, or forgets, or is on leave — the log has a gap.
How to capture bank logs programmatically
A reliable logging approach runs automatically, stores data in a structured format, and creates an immutable record at the point of capture.
The architecture is straightforward:
Bank account
↓ (scheduled sync)
BankLink Pulse
↓ (webhook)
Your system
↓
Append-only transaction log
↓
Compliance archive (5-year retention)
Each sync captures all new transactions since the last run. The webhook payload includes the full transaction detail and the running balance.
{
"account_number": "62012345678",
"bank": "fnb",
"synced_at": "2026-06-10T07:00:00Z",
"transactions": [
{
"id": "txn_9f3c21a",
"date": "2026-06-09",
"description": "SUPPLIER PAYMENT REF:INV-2891",
"amount": -48500.00,
"running_balance": 102340.00,
"reference": "FNB2026060901234",
"type": "debit"
}
]
}
Storing this payload in an append-only table gives you:
- A timestamp of when BankLink captured the transaction (
synced_at) - The bank's own reference number
- The running balance as evidence of account state
- An immutable record that can be produced in a compliance audit
Building a five-year retention store
For FICA compliance, your log needs to survive for five years and remain queryable. The simplest implementation:
CREATE TABLE bank_transaction_log (
id BIGSERIAL PRIMARY KEY,
captured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
account_number TEXT NOT NULL,
bank TEXT NOT NULL,
txn_date DATE NOT NULL,
description TEXT NOT NULL,
amount NUMERIC(15, 2) NOT NULL,
balance NUMERIC(15, 2),
reference TEXT,
raw_payload JSONB
);
-- Never update or delete rows — append only
CREATE RULE no_update AS ON UPDATE TO bank_transaction_log DO INSTEAD NOTHING;
CREATE RULE no_delete AS ON DELETE TO bank_transaction_log DO INSTEAD NOTHING;
The raw_payload column stores the full JSON from BankLink, so if you ever need to re-process or prove completeness, the original capture is there.
Handling gaps and replays
Automated log capture can miss data if a sync fails — network error, service downtime, or a missed window. Your system should:
- Track the
synced_attimestamp of every successful sync - Alert if no sync is received within the expected window (e.g., more than 25 hours for a daily Pulse)
- On recovery, trigger a manual sync that fetches back 7 days to fill any gap
BankLink Pulses are idempotent — syncing the same transaction twice will return the same data. Your system should use the bank's reference field as a deduplication key, not your own auto-increment ID.
Getting started
BankLink supports FNB transaction capture today, with more banks being added. A Pulse configured with a dashboard destination writes every transaction to the BankLink database and delivers it to your webhook — giving you a live, structured log from the moment you link the account.
