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.

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:

  1. Attacker initiates login for the victim account. The OTP SMS is sent.
  2. 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-otp endpoint.
  3. Backend exhausts SMS retries and surfaces alternative channel options to the user: "Try WhatsApp" or "Try Email."
  4. 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:

Example finding

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?

Get a security review

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