Why SMS OTP fails in Nigeria
Fintechs do not send SMS directly. They route through bulk OTP aggregators — companies like Termii, Sendchamp, or Africa's Talking — who in turn send via shared sender IDs registered on each telco's network. Those shared sender IDs get throttled. When ten fintechs are hammering the same sender ID at month-end payroll time, messages queue. Some never deliver.
There is another structural problem: DND (Do Not Disturb) registration. The Nigerian Communications Commission mandates that subscribers can opt into DND, which blocks promotional and transactional SMS categories depending on configuration. A non-trivial portion of your user base may have DND active and will never receive an SMS OTP regardless of delivery quality.
During network congestion events — end-of-month payroll periods, public holidays like Eid or Christmas, major transfer windows — OTP delivery can take 5 to 10 minutes or fail entirely. Users call support. Support agents are overwhelmed. Product teams ship a fallback to reduce call volume. The fallback is where the vulnerability lives.
The fallback landscape
Nigerian fintechs have converged on four common OTP fallback mechanisms. Each solves a real user problem. Each introduces a distinct attack surface.
- Resend via WhatsApp — the fintech sends the OTP via WhatsApp Business API to the phone number on file. Popular because WhatsApp penetration in Nigeria is effectively 100% among smartphone users.
- Resend via Email — OTP goes to the registered email address. Familiar to users. Widely implemented. Often the weakest link.
- Call me — an automated voice call reads the OTP aloud. Useful for feature phone users and high-DND environments. Rare but present, especially in older banking apps.
- Use backup code — static codes generated and displayed once at account creation. Security-forward in theory; broken in practice because most Nigerian users do not save them.
How attackers exploit each fallback
WhatsApp fallback
To intercept a WhatsApp OTP, an attacker needs control of the victim's WhatsApp account — not their phone number. That is a lower bar than it sounds. SIM swap attacks against MTN and Airtel subscribers are documented and ongoing. But more common is WhatsApp Web session theft: a victim's active WhatsApp Web session persists even after the phone is put away. An attacker with momentary physical access, or a device infected with a RAT, can silently harvest WhatsApp messages without touching the SIM.
There is an asymmetry that makes WhatsApp a preferred target: WhatsApp delivery is near-instant and provides a read receipt. The attacker knows the OTP arrived. SMS delivery is uncertain — the attacker cannot confirm the message reached the phone. WhatsApp is paradoxically a more reliable attack channel than SMS from the attacker's perspective.
Email fallback
Credential stuffing against Gmail, Yahoo Mail, and Microsoft accounts is not a niche threat in Nigeria — it is routine. Hundreds of millions of credential pairs from global breaches are in circulation and actively tested. An attacker who has already compromised a victim's Gmail account can wait for any fintech OTP to arrive. The fintech's account security has been silently outsourced to Google's security.
The email fallback also tends to have weaker rate limiting. SMS OTPs are expensive (₦2–₦8 per message depending on aggregator pricing), so teams put rate limits on them by default. Email is free, so the rate limiting discipline is often absent.
"Call me" fallback
Voice OTP delivery is exploitable via call forwarding. An attacker who can
social-engineer a victim's telco customer service agent — or who has
compromised a telco web portal account — can activate unconditional call
forwarding from the victim's number to a number they control. Every
automated OTP voice call then rings the attacker's handset. MTN and Airtel
both support USSD-based call forwarding codes (*21*[number]#)
that can be activated without physical access to the handset if the attacker
has the victim's SIM PIN or telco account credentials.
Backup codes
Static backup codes have two failure modes in the Nigerian fintech context. First: they are displayed once at account creation, and the typical user immediately closes the modal. The codes are gone. Second: some fintechs email the backup codes to the registered email at account creation — which collapses the "second factor" back into a single channel (the email inbox) that is shared with the email OTP fallback. An attacker who controls the email gets both.
The channel downgrade attack
The most effective attack pattern is not targeting any individual fallback in isolation — it is forcing the application into its weakest state. The channel downgrade attack works like this:
- Attacker initiates login for the victim account. The OTP SMS is sent.
-
Attacker clicks "Resend OTP" repeatedly — not to receive the SMS, but to
exhaust the SMS retry budget (commonly 3 attempts). This can be done
programmatically via the
/resend-otpendpoint. - Backend exhausts SMS retries and surfaces alternative channel options to the user: "Try WhatsApp" or "Try Email."
- Attacker selects the channel they have already compromised: email (if they have the victim's email), WhatsApp (if they have a WhatsApp Web session).
The more critical variant does not even wait for SMS to fail. If the backend accepts the delivery channel as a client-specified parameter, the attacker can specify their preferred channel on the first request:
POST /api/v1/resend-otp
Content-Type: application/json
Authorization: Bearer <session_token_from_login_initiation>
{
"phone": "+2348012345678",
"channel": "email"
} No SMS is sent at all. The OTP arrives in the email inbox immediately. This bypasses the entire SMS delivery problem — and bypasses the SMS channel entirely.
The vulnerable pattern in code
The root cause is trusting the client to specify the delivery channel. Here is the vulnerable pattern:
// VULNERABLE — channel is attacker-controlled
const channel = req.body.channel || req.query.channel || 'sms';
await sendOtp(userId, channel);
// ALSO VULNERABLE — preferredChannel field accepted without validation
const { preferredChannel } = req.body;
const channel = ['sms', 'whatsapp', 'email'].includes(preferredChannel)
? preferredChannel
: 'sms';
// Attacker passes 'email' and the validation passes — it's a valid enum value
await sendOtp(userId, channel); The correct pattern: the server determines the delivery channel. The client has no say.
// CORRECT — server owns channel selection
async function determineDeliveryChannel(userId: string): Promise<OtpChannel> {
const user = await db.user.findUnique({ where: { id: userId } });
// Primary: SMS always attempted first for authenticated sessions
// Fallback only after server-side verification of SMS failure
const smsDeliverable = await checkSmsDeliverability(user.phone);
if (smsDeliverable) return 'sms';
// Fallback selection is server-controlled, not client-controlled
// High-risk actions: no fallback — require support contact
if (user.currentAction === 'high_risk') {
throw new OtpDeliveryError('SMS_REQUIRED_FOR_HIGH_RISK_ACTION');
}
// Standard actions: use pre-configured secondary channel from user profile
return user.secondaryOtpChannel ?? 'email';
}
const channel = await determineDeliveryChannel(userId);
// Each channel generates its own independent OTP — never reuse codes across channels
const otp = await generateAndStoreOtp(userId, channel);
await sendOtp(userId, channel, otp); Business impact
A successful OTP bypass gives an attacker an authenticated session. From there the path is direct: wallet drain via P2P transfer, airtime purchase, or bill payment. Most Nigerian fintech wallets hold between ₦15,000 and ₦80,000 for everyday users. Salary wallets and investment accounts (Cowrywise, Piggyvest-style features embedded in neobanks) regularly hold ₦500,000 or more.
Beyond the direct financial loss, account takeover via authentication bypass has regulatory teeth in Nigeria. CBN's Risk-Based Cybersecurity Framework for Financial Institutions classifies authentication control failures as high-severity findings that can trigger examination findings during routine audits. The NDPA (Nigeria Data Protection Act) adds mandatory breach notification obligations where personal data is accessed without authorisation — a compromised account qualifies. The combination of user compensation, regulatory fines, and reputational fallout from a pattern of account takeovers can exceed ₦50,000,000 for a mid-sized fintech.
For fintechs at Series A or later, investor due diligence now routinely includes a security review. Authentication bypass vulnerabilities in a pre-close technical audit have delayed or restructured term sheets.
The fix
These are not abstract recommendations. They are the specific controls that close the attack surface described above:
- Never accept the delivery channel from the client request. The server determines which channel to use based on user profile configuration and internal delivery state. Client input is rejected.
- Generate a distinct OTP per channel. If SMS and email
both send a code, they must be different codes with independent expiry
timers. An attacker cannot receive the SMS code via email or vice versa.
Store channel affinity on the OTP record:
{ otp_hash, user_id, channel, expires_at, consumed }. - Enforce a minimum delay between channel switches. An attacker cannot switch from SMS to WhatsApp in under 60 seconds. Implement a per-user channel switch cooldown enforced server-side with Redis TTL or a database timestamp check.
- Rate limit aggressively at the phone number level. Maximum 3 OTP requests per phone number per 10-minute window across all channels. After the limit is hit, lock the account and require customer support intervention. Do not expose this limit in the API response body — return a generic error message.
- High-risk actions get SMS only, no downgrade. Large transfers (above your configured threshold), BVN change, payout account modification, PIN reset: SMS is the required channel. If SMS delivery fails for a high-risk action, the user must contact support. There is no automated alternative.
- Log every channel switch attempt. An authenticated session that triggers channel switches is a behavioural signal worth alerting on. A user who switches channels twice in one session is unusual. Feed this into your fraud scoring.
Client-specified OTP channel with no hierarchy enforcement
During a penetration test on a Nigerian payments fintech, we identified
that the /resend-otp endpoint accepted a
preferredChannel field in the request body. By setting it
to "email" on the very first OTP request — without waiting
for any SMS delivery attempt — we received the OTP in an email inbox we
controlled during the test. The backend performed no channel hierarchy
check, no delivery state verification, and no minimum delay enforcement.
SMS had been bypassed entirely on the first attempt. The OTP was valid,
the session was authenticated, and the test wallet was accessible. Fix
priority: critical.
Does your /resend-otp endpoint accept a channel parameter
from the client? When did you last verify it?
Frequently asked questions
Should we use authenticator apps instead of SMS?
TOTP authenticator apps (Google Authenticator, Authy) eliminate the SMS delivery problem entirely and are phishing-resistant. They are the right answer for sensitive operations, though adoption requires user onboarding investment.
Is WhatsApp OTP safer than SMS?
It depends on what controls WhatsApp account access. If WhatsApp is protected by the same SIM — which is common for Nigerian users — it is equally vulnerable to SIM swap attacks. Treat it as equivalent to SMS, not as a stronger channel.
How do we handle users who genuinely cannot receive SMS?
Implement a proper account recovery flow that requires human verification — customer support plus an ID document check — rather than an automated bypass. Automated fallbacks for delivery failure are where attackers focus first.
Related reading
Blog: JWT token security mistakes Nigerian fintechs make · BVN spoofing and liveness bypass in Tier-1 onboarding
Services: Authentication security testing · Fintech security checklist