Russend
Russend lets people send money from Russia to recipients in Central Africa, settling directly into mobile wallets rather than bank accounts. It ships on both the App Store and Google Play. I built the backend: the transaction lifecycle, the payment gateway integration, and the real-time layer that tells a sender where their money is.
- Year
- 2024
- Role
- Backend, payment gateway integration, API integration
- Context
- Client product
- Category
- Fintech

The product surface - a single question answered clearly.
The recipients this product serves largely do not hold bank accounts - they hold mobile money wallets on Orange Money, MTN and similar networks. Each of those networks has its own rails, its own failure modes, and its own idea of what a completed transfer means. Meanwhile the sender, several thousand kilometres away, wants a single honest answer to one question: has it arrived?
Transfers are modelled as a state machine rather than a request. Every transfer moves through explicit states - initiated, funded, dispatched, settled, failed - and each transition is written before the external call that triggers it, so a gateway timeout leaves a recoverable record instead of an ambiguous one. Payouts run through the Paydunya API, which aggregates the regional mobile money networks behind one integration surface. Socket.IO pushes each state change to the sender's device as it happens, with Firebase push notifications covering the case where the app is not open, so tracking is a live view of the record rather than a polling loop.
How the pieces fit.
API
Express.js services owning the transfer lifecycle, authentication, and gateway callbacks.
Payments
Paydunya API for mobile money payouts across Orange Money, MTN and other regional networks.
Data
MongoDB storing transfers as append-friendly documents, so the history of a transfer survives its final state.
Realtime
Socket.IO channels scoped per user, pushing transaction status transitions to the sender's device.
Push
Firebase Cloud Messaging for transfer status updates when the app is backgrounded or closed.
Delivery
GitHub Actions for automated testing and deployment, behind an Nginx reverse proxy on DigitalOcean.
Deployment
- Hosted on DigitalOcean
- Nginx as reverse proxy and for server management
- GitHub Actions running the CI/CD pipeline
- Domain via Namecheap with DNS configured for the API and app surfaces
- Mobile money payouts via Paydunya across Orange Money, MTN and other regional networks
- Transfer lifecycle modelled as explicit states with recoverable transitions
- Real-time status tracking from initiation through to settlement over Socket.IO
- Firebase push notifications for status updates outside the app
- Automated testing and deployment pipelines through GitHub Actions
The parts that were not obvious at the start.
- 01
Gateway callbacks are not guaranteed, ordered, or unique
Payment providers retry webhooks, deliver them out of order, and occasionally deliver them twice. Every callback handler had to be idempotent and keyed on the provider's transaction reference, so replaying a webhook converges on the same state instead of double-crediting a recipient.
- 02
Money crossing a currency and network boundary
A transfer is not one operation. It is a debit in one currency, a conversion, and a credit on a network with different settlement timing. Treating it as a single unit of work would have made partial failure invisible; treating it as staged transitions made every partial failure a state you can query and resume from.
- 03
Mobile networks drop connections
Real-time status over Socket.IO is only useful if it survives a tunnel, a lift, and a carrier handover. The client reconciles against the transfer record on reconnect rather than trusting that it received every event.
- Live on the App Store and Google Play
- Senders track transfers in real time from initiation to arrival
- Payouts reach mobile wallets across multiple Central African networks through one integration
- Write the state transition before the network call, never after it.
- Every external financial API will eventually send you the same event twice. Design for it on day one.
- The sender's real question is never 'did the API return 200'. It is 'did my mother receive it'. Model the thing the user cares about.