Attacks will land. Design so they stay small.
The attacks that reach payment products in emerging markets, and the boundary that keeps each one contained: the edge, the device, the account, the partner, the transaction, the ledger and the tenant.
Most security writing for fintech is about keeping attackers out. That work matters, but it isn't what decides whether an incident costs you an afternoon or a licence. What decides it is how far an attack can travel once something gives way: a stolen SIM, a leaked partner key, a rooted phone, a payout rail that times out at the worst possible moment.
I ran security and compliance for a cross-border payments platform as its ISMS Manager, and before that I built wallets, lending apps and card-terminal software for banks and fintechs. The habit that has served me best is the one ship designers use: assume a compartment will flood, and make sure the water stops at the bulkhead.
Draw the money path first
Network diagrams show where packets go. Attackers care about where money goes. So every threat review starts by tracing one transfer from the tap to the auditor: the phone, the edge, authentication, the risk decision, the ledger, the payout rail and the evidence trail. Each hop is a boundary, and each boundary gets three questions.
- What does it stop? Name the attacks, not the control.
- How does it fail? Open or closed, and who notices.
- Who can switch it off, and how fast? Ideally without a deploy.
The rest of this piece walks that path, one boundary at a time.
1. The edge: absorb the noise without becoming the outage
What arrives here: credential stuffing, scraping, layer 7 floods, injection probes and every scanner on the internet.
The obvious answer is a web application firewall, and you should have one. The less obvious lesson is that the WAF is itself a failure domain. We once lost staging and production to a self-managed ModSecurity layer running the OWASP Core Rule Set: the control meant to protect the platform became the thing that took it down. We removed it, moved the edge to AWS WAF with managed OWASP rules on the load balancers, and it hasn't happened again.
What I took from it: run new rules in count mode before block mode, rate-limit by route and by identity rather than only by IP, and make sure a bad edge rule can be rolled back in minutes by someone other than the person who wrote it.
2. The device: treat integrity as a signal, not a gate
What arrives here: rooted and jailbroken phones, Frida hooks that rewrite your app at runtime, repackaged APKs and emulator farms opening accounts in bulk.
Certificate pinning and secrets in the Keychain or Keystore are table stakes. Above that, read the device's integrity signals: root, jailbreak, hooking frameworks, tampering. That is why I open-sourced device_sentinel_security for Flutter. But don't hard-block on those signals. Plenty of honest customers in emerging markets run cheap, modified Androids, and a false positive at login is a lost customer.
Feed the signal into the risk decision instead. A tampered device can sign in and see its balance. It can't add a beneficiary or move a large amount without stepping up, and its limits stay low. The attack is contained to a session that can't do much.
3. The account: assume the SIM is already gone
What arrives here: SIM swaps that hijack SMS one-time passwords, social engineering, and passwords reused from someone else's breach.
In markets where the phone number is the identity, a SIM swap is the master key. So build the account boundary around the device, not the number. Bind each session to a device ID from the first tap and treat a new device as a new risk event: limits drop, new beneficiaries wait, and high-value actions need a second factor that isn't SMS. Where a carrier exposes how recently the SIM changed, that belongs in the score.
This is the reasoning behind the device-change detection in the transaction-monitoring system I designed at Cede. The point isn't to catch every takeover at login. It's to make sure a takeover that gets through can't empty the wallet before anyone notices.
// A new device is a new risk event: the account still works,
// but what it can do shrinks until the device earns some history.
func Limits(a Account, d Device, now time.Time) Policy {
p := a.Tier.Limits()
if d.FirstSeen.After(now.Add(-72 * time.Hour)) {
p.DailyOut = min(p.DailyOut, a.Tier.NewDeviceCap)
p.NewBeneficiaryHold = 24 * time.Hour
p.StepUp = StepUpNonSMS
}
if d.Integrity.Tampered() {
p.DailyOut = min(p.DailyOut, a.Tier.TamperedCap)
p.StepUp = StepUpNonSMS
}
return p
}4. The customer: when the user is real and the intent isn't
What arrives here: authorised push payment fraud. The customer is genuine, the device is theirs, the password is right, and they have been talked into sending money to someone else. No authentication control stops this, because nothing is wrong with the authentication.
The boundary here is friction in proportion to risk. First payments to a new beneficiary are smaller or delayed. Track velocity per beneficiary as well as per sender, because one mule account receiving from many victims is a stronger signal than one victim sending many times. And the confirmation screen should name the risk in plain words, in the customer's language, at the moment it matters.
5. Onboarding: give new identities a small blast radius
What arrives here: synthetic identities, forged documents, borrowed BVNs and NINs, and mule accounts opened in batches.
KYC will never be perfect, so tier it. A new account starts with low limits that rise as it builds history, and every tier upgrade is a fresh check. Then link the graph: a shared device, a shared payout account or the same identity number across accounts tells you more than any single document check. A batch of mule accounts usually looks clean one at a time and obvious together.
6. Partners: their breach must not become yours
What arrives here: stolen API keys, replayed requests, and the partner whose own systems are compromised and start sending you perfectly authenticated garbage.
For Cede's institutional partners we used HMAC-SHA256 request signing, verified in middleware against a timestamp window, and mapped each partner to a shadow user. The shadow user is the isolation: downstream handlers authorise a partner exactly as they would any other user, so a partner's key can only ever reach that partner's own resources. Business logic never had to learn a second auth model, which also meant the change shipped without touching existing endpoints.
Give each partner its own keys, its own limits and its own kill switch. When a partner has a bad day, you want to turn off one integration, not the platform.
7. The transaction: decide inside the payment path
What arrives here: velocity attacks, card testing with small amounts, fan-out to many recipients, and transfers from places the customer has never been.
A fraud review that runs overnight finds the money after it has left. At Cede I designed transaction monitoring that sits inside the payment path, with velocity rules, tracking across platforms, geo-fencing and device-change detection, returning allow, review or block while the customer waits.
That puts a latency budget on the decision, and the budget forces an honest question: when the risk engine is slow or down, do you fail open or closed? Answer it per corridor and per amount, in writing, before the incident rather than during it. A small transfer to a known recipient can fail open with a review flag. A large first-time transfer fails closed.
8. The ledger and the rail: make double spending impossible, not unlikely
What arrives here: replayed requests, race conditions between two devices, and the most expensive bug in payments, resending a payout because the rail timed out.
- Idempotency keys from the first tap, so a retry is the same transfer, not a second one.
- Double-entry posting in one database transaction, with a constraint that refuses to commit when debits and credits disagree. Balances are derived, never edited.
- Timeouts become questions. A rail timeout triggers a status query, never a resend, and each rail sits behind a circuit breaker.
- Limits per corridor, so a fault in one corridor can't drain treasury in another.
None of these are fraud controls in the usual sense, but they are what stops an attack at the edges from becoming a loss on the books.
9. Tenants and infrastructure: separate the data, not just the logo
What arrives here: one white-label client's data surfacing in another client's app, over-broad cloud permissions, leaked secrets and insiders.
On a white-label wallet platform shipped in five markets, theming made each client's app look different. It did nothing to keep their customers apart. Each tenant got its own data layer, so one client's customers could never surface in another's app. I wrote the approach up for IT Pulse.
The cloud follows the same principle: production in its own account, least-privilege roles, threat detection with GuardDuty and Security Hub, and audit logs retained by policy where nobody can quietly edit them, including the people who run the platform.
Kill switches are part of the design
Every boundary above needs an off switch that works without a deploy. We run trunk-based development with Unleash feature flags, which is usually described as a delivery practice. In security it is a containment tool: a corridor, a partner, a payout rail or a risky feature can be switched off by market in seconds, and back on just as fast once the incident is over.
Evidence is a side effect, not a project
The last stop on the money path is the auditor. SOC 2, PCI DSS v4.0.1 and ISO 27001 ask the same question in different words: can you show what happened? If the infrastructure produces its own evidence (locked logs, recorded decisions, versioned rules), an incident review and an audit both take a query instead of a week. In our live PCI DSS audit, the InfoSec roles and responsibilities framework mattered as much as any technical control, because it said who owned each boundary when something went wrong.
The checklist I use
For each hop on the money path:
- Name the attacks it stops, and the ones it doesn't.
- Decide how it fails, open or closed, before you need to.
- Cap what a single compromised account, device, partner, tenant or rail can move.
- Give it a kill switch that works without a deploy.
- Make sure it leaves evidence nobody can edit.
Prevention will fail eventually. Design so that when it does, the damage stays small, the switch is within reach and the story can be told from the logs.