A community savings platform for South African stokvels - built to work across USSD, mobile, and web, from feature phones to smartphones.
A stokvel is a rotating savings club - a practice woven into South African community life for generations. Members pool money on a regular schedule and the pot rotates among them, giving each person access to a lump sum they couldn't easily save alone.
An estimated 11 million South Africans participate in stokvels, collectively managing over R50 billion a year. Almost all of it is tracked through spreadsheets, WhatsApp groups, and handshakes. eStokvel replaces that with a proper digital platform - member management, contribution tracking, payout scheduling, and financial history.
The hard part wasn't building one interface - it was building three that all talk to the same underlying system. South Africa's digital divide is real: group secretaries might be on a laptop, urban members on a smartphone, and rural members on a basic feature phone with no data plan.
USSD is the key constraint. It's the technology behind short codes like *120*321# - works on any mobile phone, no internet required. But USSD is fundamentally stateless: every keypress triggers a fresh HTTP request to a webhook. No persistent connection, no cookies - just a new call each time the user presses a digit.
That means every piece of session state - which menu the user is in, what they're entering, which group they're managing - has to be stored externally and reconstructed on each incoming request. The 182-character-per-screen limit adds another constraint: every menu has to be ruthlessly concise.
A single Express API serves all three surfaces. USSD requests arrive as webhooks from Africa's Talking; REST calls come from the mobile and web apps. All three share the same business logic, database, and auth layer - a bug fix or feature addition applies everywhere at once.
USSD has no concept of a persistent session - each keypress is an independent HTTP webhook call. Redis stores the user's current menu position and any in-progress input (like an amount being typed) with a 5-minute TTL. If a user drops off mid-flow, the Redis key expires cleanly. This was the most novel constraint I had to design around - managing a stateful UX on a stateless protocol.
For a financial platform, schema drift caught at compile time is worth the abstraction. Prisma's type-safe queries mean a mismatched column type or missing field is a build error rather than a silent runtime bug in production. That matters when the data represents real money - a wrong type on a contribution amount has real consequences.
Rather than separate backends for USSD, mobile, and web, all three call the same Express API. The USSD handler is just another controller that maps keypad inputs to the same service layer the mobile app uses. This keeps business logic in one place - a fix or new validation rule applies to every interface automatically.
Active development. The core backend - members, groups, contributions, payouts - is built and tested. The USSD flow covers the primary journeys: join a group, make a contribution, check balance, view payout schedule. The React Native mobile app is in progress; the web admin frontend is next in the queue.
This is the kind of project I love: real users, real constraints, and systems that have to be correct because people's money is involved.