New: Truss Core Banking and Truss mobile apps. See how the products fit together →
Truss Core Banking

Core banking that balances to the kobo.

General ledger, savings and current accounts, loans, the overnight batch and an audit log, built for microfinance and digital banks in Nigeria. The rules that keep the books right live in the database, so a bug, a retried request or a stray script can’t unbalance them.

0unbalanced entries saved
4 dpfixed-point amounts
100%of writes audited
1source of truth
Journal entry JE-20418 posted · business date 14 Mar
Dr  Customer savings 001225,000.0000
Cr  NIP settlement suspense24,975.0000
Cr  Transfer fee income25.0000
Debits = CreditsBalanced · posted
Journal entry JE-20419 attempted
Dr  Customer savings 004410,000.0000
Cr  NIP settlement suspense9,000.0000
Off by 1,000.0000Rejected · nothing written

Example entries. The database checks every entry when it commits. If it doesn’t balance, none of it is saved.

Behind the scenes

What happens when a posting is made.

This loop shows a transfer being posted, then a faulty entry being refused. It follows the same steps the real system takes.

Transfer: ₦25,000
☷
App or tellerrequest sent once
⇄
Posting APIbuilds the entry
∑
Balance checkat commit
▤
Ledgerappend-only
⇨
Event queuesame transaction
⛓
Audit trailhash-linked
₦25,000
Step 1

A customer sends ₦25,000.

Ledger2 entries
JE-20416Dr Loans 700,000.0000✓
JE-20417Dr Savings 2,000.0000✓

Simplified. Figures are examples.

Modules

Six services, one ledger.

Each service keeps its own data. Only the ledger’s posting function can write to the books.

∑

General ledger

Double-entry postings against your chart of accounts. A reversal is a new entry that cancels the original. Nothing is edited.

▤

Accounts

Savings and current accounts with versioned products, holds, interest accrual and automatic dormancy.

₦

Loan book

Application to closure: two-person approval, payout, repayment schedules, restructuring, refinancing and write-off.

☾

Overnight batch

Processes every account each night, resumes safely after a crash, and only moves the date on when every account is done.

⛓

Audit log

Every change is recorded as an event linked to the one before it by a hash. Editing an old event breaks the chain.

⇄

Continuous reconciliation

Balances and sub-ledgers are reconciled on a schedule. Any difference is raised and escalated.

General ledger

The database enforces the rules.

Many ledgers rely on the application code to get things right. Truss checks again at the database.

  • Balanced or refused. A check at commit rejects any entry where debits and credits differ. Nothing is saved.
  • Append-only. Journal lines are never updated or deleted. A reversal adds one new entry and marks the original as reversed, in a single step.
  • Locked down. The application’s database user can’t write to ledger tables. Only the posting function can, and tests confirm it.
  • Exact amounts. Four decimal places, fixed-point, with the currency stored alongside, from the API to disk.
Accounts / Loans app credential Script or bug UPDATE journal_line ... post_entry() sanctioned function Ledger journal_entryjournal_linebalances RLS · append-only Refused, and the attempt is logged balance checked at commit
Accounts

Clear rules for every account status.

Savings and current accounts follow a fixed lifecycle. Each status change is checked by the service, by a database constraint that won’t save an invalid move, and in the same transaction as its history record.

  • Versioned products. Changing a rate doesn’t alter the terms an existing customer signed up to.
  • Holds and reservations. Funds checks can be safely retried, so a repeated withdrawal request isn’t counted twice.
  • Interest and dormancy run as named steps in the overnight batch.
Pending open Active Dormant Closed funded + KYC no activity reactivated zero balance,no holds or loans zero balance abandoned after 30 days
Loans

Every loan decision is on the record.

A loan’s status (where it is in its life) is kept separate from its classification (how it’s performing). Reclassifying a loan never changes its status, and each change raises an event finance can use for provisioning.

Application Approved Disbursing Activeschedule v1 generated Restructured Refinanced Written off Closed Rejected secondapprover ledgerconfirms fully repaidreject or withdraw

Prudential classification

Each night the batch works out days at risk from the oldest unpaid instalment and places every active loan in the bank’s configured band. Interest is suspended where the band requires it.

Performing
Watch
Sub­standard
Doubtful
Lost

Repayment schedules

Declining-balance annuity schedules with correct month-end dates: a loan taken out on 31 January is due at the end of February. Schedules are versioned, so a restructure keeps the history.

Safe payouts

A loan only goes active once the ledger confirms the payout. If confirmation doesn’t arrive, the loan stays in ‘disbursing’ and the reconciler follows it up.

Overnight batch (end of day)

Every account, every night.

If a step fails on one account, that account is set aside and flagged for the team. The business date only moves on once every account has finished every step, and the system checks this before it advances.

1
Close the dayper branch
2
Overdue chargesapplied
3
Arrears checkeach instalment
4
Classify loansdays at risk
5
Accrue interestperiodic entries
6
Deposit maturityfixed terms
7
Dormancy sweepinactive accounts
✓
Next dayafter a final check

Picks up where it stopped

Each account records the last step it finished, so after a crash the batch carries on without posting anything twice.

Runs in parallel

Accounts are split across workers. If a worker stops, its lease runs out and another worker takes over.

Set up per bank

Steps are named, ordered and switched on for each institution, with a permanent log of every run.

Audit log

An audit trail you can verify.

Every change to data produces an audit event, and the build fails if any write path is missing one. Events are linked with SHA-256 hashes, so editing an old event breaks every link after it. The latest hash is also stored outside the system and checked regularly.

#10,241loan.disbursedprev 9f2c…a41ehash 3b7d…c09f
#10,242account.hold.placedprev 3b7d…c09fhash e81a…47b2
#10,243journal.postedprev e81a…47b2hash 5c90…d1e3
#10,244loan.reclassifiedprev 5c90…d1e3hash 0a6f…9b58
Merkle rootanchored externallyroot 7e44…18acchecked regularly

Example hashes. Reliability uses the same library to link its reconciliation records.

Insert-only

The audit tables accept new events and nothing else. Attempts to update or delete are refused and logged.

Emergency access

Break-glass access uses a separate role that needs approval and leaves a full record.

Privacy requests

Personal data can be erased on a valid request without breaking the hash chain.

Under the hood

Proven tools, used carefully.

Go

Go throughout

Every service is written in Go, with decimal arithmetic for all amounts.

PG

PostgreSQL

One high-availability cluster with a separate database per service. Nothing else holds the official record.

⇆

Reliable events

Events go out through a transactional outbox and change data capture, so what’s published matches what was saved.

K8s

Kubernetes and GitOps

Deployments are defined in Git and applied automatically. Services talk to each other over mutual TLS.

▣

Separate data per bank

Row-level security on every table, tested on every build, so one institution can’t see another’s data.

✓

Thorough testing

Component tests run against real Postgres. Property-based tests cover every rule. Each release is signed off by an independent tester.

See a posting, a reversal and a night run.

We’ll run them live for your team, check the audit trail, then take your architects through the design.

Book a walkthrough