Case studies redrawn from scratch and generalised: no partner names, internal systems or figures beyond my CV.
I design how money, data and systems move between a product, its partners and its users. These cases show the problem, the architecture I drew, the decisions behind it, and what I would do differently.
Case 1 · Lending fintech · Gulf → Southeast Asia
From a remittance MVP to relatives repaying a credit line from abroad
IdeaCapture migrant remittances that already flow to the families we lend to
ModelOur margin plus interbank FX, negotiated with the partner until volume tiers
The market
The Gulf is the largest source of remittances in the world: GCC countries sent out about $161B in 2025, with the UAE and Saudi Arabia at roughly $62B and $58B. For scale, one corridor of the kind we worked on, UAE and Saudi Arabia to the Philippines, officially carries around $4B a year (4.6% and 6.6% of the Philippines' $35.6B in 2025 cash remittances), and the real figure is higher, because transfers are recorded where the remitting institution sits. Even a 1% share of that one corridor is about $40M a year flowing to families who are, or could become, our borrowers.
Sources: GCC-Stat 2025 outward worker remittances; Bangko Sentral ng Pilipinas 2025 cash remittances by source country. The share scenario is illustrative.
The problem
Many of our borrowers' families depend on relatives working in the Gulf. That money already crossed borders every month, but through other apps, so we saw neither the revenue nor the income it proved. The goal was to bring that flow into our product fast, without building a cross-border stack first.
moneydata / statusexternalholds money
How a transfer works
Pay-in. The sender pays the remittance provider in AED or SAR by card, wallet or cash. The provider runs sender KYC.
Identity reuse. The provider shares a KYC ID; we store it and call the provider with it on every next transfer, so the sender is verified once. On our side the relative's kinship is confirmed with a document, kept in the PII vault.
FX and payout. The provider converts AED/SAR → USD → local currency at interbank rate and calls our payment service with an idempotent transfer request.
Booking. The ledger posts the incoming funds; billing books our margin.
Destination. In the MVP the money lands in the recipient's wallet. After the pivot, the credit engine applies it as a repayment to the relative's credit line.
Status back. Final status goes back to the provider; the relative sees it in the app.
Data for lending. Transfer history feeds scoring as an income signal for the recipient's credit decisions (piloted).
One repeat transfer, step by step (after the pivot)
money movementcallresponse / callback
Key decisions
Buy first, build later
A remittance-as-a-service provider gave us licensed pay-in, KYC and FX in the Gulf from day one. An in-house build only makes sense once volume justifies the licences and the margin.
Interbank FX until volume tiers
I negotiated interbank FX with the partner until we reached agreed volumes, so our margin was the only spread the sender paid, and the price stayed competitive with existing apps.
Verify the sender once
Reusing the partner's KYC ID removed repeat verification for every transfer; our own check covered only what we owned: the family link to the borrower.
Pivot to repayments
Paying straight into a relative's credit line tied the flow to our core product: the money now reduces credit risk instead of only sitting in a wallet.
What happened, and what I'd do differently
The scoring pilot launched, then stalled on two constraints: team funding, and the licence question of receiving funds at volume. Next time I would put the licensing path and its volume thresholds into the business case at the discovery stage, and size the team for the in-house phase before the MVP goes live, not after.
Generalised from my work at a lending fintech. Diagram redrawn from scratch; component names are generic.
Virtual IBANs inside loan origination: bank-transfer repayments that reconcile themselves
ProblemBank-transfer repayments had to be matched to loans by hand
ScopeVIBAN issuance in origination, async bank API, matching, repayment events
ContextRepayments with 2 partners, 3 methods each: cards, bank transfer, cash
Result100% of bank-transfer repayments reconcile automatically
The problem
When customers repay by bank transfer to one shared account, every payment has to be matched to a loan by name or reference, and people get references wrong. The fix is to give each loan its own virtual account number: whatever arrives on it already says whose repayment it is.
moneydata / statusexternalholds money
Issuance and the first repayment, step by step
money movementcallresponse / callback
Key decisions
Issue the VIBAN inside origination
The account number exists before the first repayment is due, so the customer gets payment details together with the loan and no repayment ever arrives without a key.
The account number is the matching key
Matching stops depending on what the customer typed in the reference: the destination itself identifies the loan, which is what makes 100% automatic reconciliation possible.
Design for a slow, asynchronous bank API
Creation is async with callbacks; timeouts trigger a status check by request ID and a retry with the same ID, never a blind second request.
Clean up duplicates explicitly
If a bank glitch ever produced two accounts for one loan, we closed the earlier one and kept the last created, so a loan never has two live repayment addresses.
One account per loan, same lifecycle
The VIBAN is opened with the loan and closed with it. A late transfer to a closed loan bounces back to the sender instead of landing in a suspense account.
Amounts that don't match the schedule
Overpayments wait on a technical account and lower the next payment, or go to partial early repayment if the customer asks. Underpayments wait too, and the app shows how much is missing for the minimum payment and nudges a top-up.
Result
All bank-transfer repayments now reconcile automatically, and the same payment event model feeds the credit engine for cards and cash, so every repayment method lands in one place.
Generalised from my work at a lending fintech. Diagrams redrawn from scratch; component names are generic.
From two external orchestrators to our own payment orchestration
Before2 external orchestrators, plus direct splits between two PSPs in some countries
ScaleFrom 4 to 40+ countries, 2–3 providers per country on average
Savings~$100K/month (~20%) in processing costs from cost-aware routing
Reliability96.4% provider SLA with health monitoring and automatic failover
Why we built our own
Problem
What we built
Outcome
Orchestrator fees grew with every new country and every extra transaction
Direct connectors to each PSP and routing by provider cost, limits and coverage
~$100K/month saved (~20%)
Vendor lock-in: two orchestrators is too few to negotiate or switch
Our own layer where the old orchestrators are just two connectors among many; card tokens in our own vault
Adding or switching a provider no longer depends on a vendor
We set the routes, but inside someone else's system
Routing rules in our admin panel: business sets the commercial part, support and product react to drops
Routing changes without a release, by the people who own the outcome
A mix of end-provider errors, mapped somehow by the orchestrators
One internal transaction state model and our own status and error mapping per provider
Soft and hard declines handled consistently; outcomes are auditable
Incidents were reactive: we waited and controlled nothing
Health monitor on queue length, response speed and SLA, with failover and gradual return rules
96.4% provider SLA
Target architecture
payment trafficrules / signals / dataexternal
Provider health: failover and gradual return
State
Typical trigger
What routing does
Degraded
Success rate 5+ pp below the provider's baseline for ~5 min, p95 response time over 2× normal, or a growing pending queue
Alert, keep routing, watch closely
Excluded
Timeouts or errors above 15–20% for 3–5 min, pending queue over its limit, or an SLA breach
No new traffic; soft declines fail over to the next provider
Recovering
Health checks pass again
5% → 25% → 50% → 100% of the normal share, 10–15 min per step; any relapse goes back to Excluded
Values are illustrative and follow common industry practice; production thresholds were tuned per provider and country.
One driver top-up, step by step
money movementcallresponse / callback
How we moved 40+ countries without a big-bang switch
1 · Orchestrators go under our layer
The two external orchestrators were connected to our orchestration as ordinary connectors, so every payment already went through our routing and state model.
2 · Dial traffic over to direct PSPs
Country by country, traffic moved from the orchestrators to direct PSP connectors in steps, with the old path one rule away if something went wrong.
3 · Migrate tokens provider by provider
Cards started on provider tokens; we migrated them over a secure channel, one provider at a time, into our own vault. Going by provider rather than by country matched how the tokens were actually held.
4 · Keep a share on the orchestrators
We agreed with the orchestrators to keep part of the traffic: a live backup route, and an integration that stays working instead of rotting unused.
Key decisions
Rules in our admin, owned by the business
Commercial routing is a business decision, so the business sets it; support and product step in on drops and failures without a release.
Classify declines before retrying
Soft declines can fail over to another provider; hard declines cannot. One internal state model makes that rule the same in every country.
Exclude fast, return slowly
A degraded provider leaves the routing quickly but comes back in steps, so one recovery blip does not hit full traffic.
Own the card vault
Tokens in our vault (PCI DSS Service Provider) mean cards are not tied to the PSP that first saw them, which is what makes routing across providers real.
Generalised from my work at a ride-hailing super-app. Diagrams redrawn from scratch; provider names omitted.
A lender lives on two money flows: the loan going out and the repayments coming back. Payout partners answer slowly and sometimes not at all; repayments arrive through cards, bank transfers and cash desks, each with its own statuses. If either side loses a status, someone ends up searching for a missing payout or a repayment that never reached the loan.
Architecture
moneyrequests / eventsexternalholds money
A loan payout, step by step
A repayment, step by step
Key decisions
A backup payout partner from day one
Payouts are the moment the customer is waiting for. A second partner turns a partner outage into a slower payout instead of no payout.
Retry on a ladder, then fail over
Status check and retries with growing intervals up to a timeout, always with the same idempotency key, so a slow partner never produces a double payout.
Link every repayment to the contract at the start
The payment knows which loan it belongs to before money moves: a QR per payment for cash, a VIBAN per loan for transfers. Nothing has to be matched afterwards.
Failures are events, not silence
Success and failure both go to Kafka. The credit engine, DWH and origination see the same truth, and the customer hears about a failure from a push, not from support.
Money logic stays in the credit engine
Balances, minimum payment and the full-payoff amount are calculated in one place; the payment service moves money and reports what happened.
Reconcile through the partner API
Statuses are checked against the partner's API rather than files and spreadsheets, so a mismatch shows up the same day.
Generalised from my work at a lending fintech. Diagrams redrawn from scratch; partner names omitted.
Case 5 · Payments and lending · in-house fraud services
Payments as the data source for fraud controls
My roleOwner of the payment data and integration points; consultant on the rules, not the fraud product owner
ScopeCard payment rules, chargebacks, checks at loan issuance, payout protection
SetupIn-house fraud services, no external vendor
What I broughtWhich signals payments can give, and where in the flow a check can stop money
The problem
A fraud team can only stop what it can see in time. Most of the useful signals are born in payments: declines and their reasons, 3DS outcomes, chargebacks, cards reused across accounts, a payout destination that changed an hour before the payout. My job was to make those signals reliable and to give the fraud services a place in the flow where a decision actually changes what happens to the money.
Where fraud checks plug into the money flow
money flow paused for a decisiondecisions / signalsfraud check point
What I worked on
Clean signals from payments
One internal state model and unified decline codes meant fraud rules could rely on "insufficient funds" and "stolen card" meaning the same thing across every provider and country.
3DS as a dial, not a switch
Helping decide when to step a payment up to 3DS: a lever between conversion and fraud losses, rather than "always" or "never".
Chargebacks back into the loop
Chargeback and dispute outcomes returned as events, so a card or account that cost money once is visible to the next check.
A hold point before payouts
A payout can wait for review without breaking the flow: hold is a normal payout state with its own push to the customer, not a stuck transaction.
What this taught me
Most fraud controls fail at the plumbing, not at the model: a rule is only as good as the signal it reads and the point in the flow where it can still stop the money. I'm comfortable on that boundary and would grow into owning the rules and the review operations themselves.
Generalised from my work at a ride-hailing super-app and a lending fintech. I was the payments owner and a consultant to in-house fraud teams, not the owner of the fraud product.