Kolo
Digital infrastructure for Ajo and Esusu savings groups — a double-entry ledger, HMAC-verified Nomba webhooks, and 14 background queues behind 32 repositories.
- 32
- Prisma repositories
- 18
- Controllers
- 14
- Background queues
- 3
- Role dashboards
- Role
- Backend engineer
- Year
- 2026
- Type
- Cooperative savings & payments infrastructure
01Overview
Kolo digitises traditional African cooperative savings — Ajo, Esusu, thrift contributions — for the millions of people who run them today on notebooks, spreadsheets and WhatsApp.
A Fastify 5 backend over PostgreSQL and Redis, with a React 19 frontend and Nomba as the payment rail. Eighteen controllers, thirty-plus services, thirty-two Prisma repositories, and fourteen BullMQ queues.
The whole thing also ships an offline demo mode that simulates every dashboard with no backend at all, so the product can be shown to a savings group on a phone with no connection.
02The problem
A savings group is a ledger with social enforcement. Everyone contributes on a schedule, one member collects each round, and the entire thing runs on the members trusting the person holding the book. Digitising it means the software has to be more trustworthy than the notebook, not merely faster.
That makes money movement the hard part. A contribution is not done when the payment provider says so — it is done when the group's wallet, the member's contribution record and the ledger all agree. Any path where those three can disagree is a path where somebody's savings quietly go missing.
And payment webhooks are hostile input: they arrive out of order, arrive twice, and can be forged.
03Architecture
- 01
Double-entry ledger
Contributions, payouts and fees are ledger entries, not balance updates. A group wallet's balance is derived, so a disagreement between what a member paid and what the group holds is reconstructable rather than a mystery — the same reason banks do it this way.
- 02
Webhook path, hardened
Nomba posts a signed webhook. The signature is verified with HMAC before anything else happens, the raw event is persisted as a WebhookEvent, and only then is a verification job enqueued. The job independently calls back to verify the payment rather than trusting the payload.
- 03
One transaction per settlement
When verification confirms, a single Prisma $transaction updates the payment to SUCCESSFUL, atomically credits the group wallet, writes the ledger entries and marks the contribution PAID. Either all four land or none do.
- 04
Fourteen queues
BullMQ over Redis with ten-plus processors: webhook processing, payment verification, payout approval, notification fan-out, email delivery, scheduled contribution reminders. Anything slow or retryable is off the request path.
- 05
Layered backend
Route registry → middleware chain (CORS → Helmet → rate limit → auth → role → group) → 18 controllers → 30+ services → 32 Prisma repositories. Group membership is a middleware concern, so no service has to remember to check it.
- 06
Three dashboards, three roles
Members see their contributions and payout position. Group admins run plans, approvals and membership. Platform operators see the whole estate. Each is its own route tree under one SPA.
- 07
Offline demo mode
A complete simulation of every dashboard with realistic mock data and no backend — pick a role, sign in with a demo password, explore. It exists because the people this is for are not going to install anything to evaluate it.
04Key decisions
Double-entry from the first commit
Storing balances and adjusting them is faster to build and impossible to audit. On software that holds other people's savings, the ledger is not an accounting nicety — it is the reason anyone should believe the number on the screen.
Verify the webhook, then verify the payment
HMAC proves the message came from Nomba. It does not prove the payment succeeded. Persisting the event and re-verifying out of band means a replayed or malformed webhook cannot credit a wallet.
Nomba over a global processor
The rail has to work for a cooperative in Nigeria — local bank transfer, local settlement, local support. A gateway optimised for card payments in another market is the wrong tool however good its SDK is.
Ship a demo that needs nothing
The evaluation problem for this product is trust, and trust needs a hands-on look before any credentials exist. An offline demo is a feature of the sales process, not a developer convenience.
05Stack
- TypeScript
- Fastify 5
- Prisma
- PostgreSQL 15
- Redis 7
- BullMQ
- React 19
- Nomba
- Argon2
- JWT
- Nodemailer