The operational pressure to fail open
Transaction success rates are the primary metric that Nigerian fintech product managers track. If a customer tries to sign up on your app and the NIBSS Name Enquiry (NE) service or the NIBSS Instant Payment (NIP) gateway fails, the product looks completely broken. This is not a rare event. Outages at NIBSS, Wema Bank, or Providus Bank happen weekly.
Under intense pressure to keep signup and checkout conversion rates high, engineering teams make dangerous design choices. They cache the results of past queries, skip validation checks entirely, or write custom fallback paths. They assume the upstream service will be back online in a few minutes and that they can verify the data retroactively. Fraud rings in Lagos watch for these exact connection drops. They script automated attacks that exploit these windows before the platform can re-establish connectivity.
Skipping validation during downtime means checking out a customer or verifying an identity becomes a guessing game. When you bypass the real-time check, you are accepting unverified data into your systems. In the context of the Central Bank of Nigeria (CBN) regulations, this operational compromise creates severe security flaws.
Attack scenario 1: Bypassing KYC verification
When onboarding a new user, fintechs must validate their identity using the Bank Verification Number (BVN) or National Identification Number (NIN). This is usually performed via APIs provided by NIBSS or the NIMC, often through aggregator services like Dojah or Smile ID.
When these upstream identity registries timeout, a poorly written backend might handle the exception by failing open. The code catch-block logs a timeout error but allows the registration process to complete anyway. The application marks the user account as verified so that the user does not leave the onboarding funnel.
An attacker registers hundreds of fake accounts during this connection failure. They use randomly generated or stolen BVNs. Because the backend skipped active validation, these unverified wallets receive tier-1 or tier-2 status immediately. By the time NIBSS or Dojah returns online, the attacker has already used these wallets to receive and withdraw illicit funds.
Here is an example of an insecure API handler that fails open during a timeout:
// INSECURE: Failing open on upstream API timeout
app.post("/api/v1/kyc/verify-bvn", async (req, res) => {
const { bvn, userId } = req.body;
try {
const verification = await dojahClient.verifyBVN(bvn);
if (verification.status === "SUCCESS") {
await prisma.user.update({
where: { id: userId },
data: { bvnVerified: true, tier: "TIER_1" }
});
return res.status(200).json({ status: "success" });
}
} catch (error) {
// The critical flaw: failing open on connection issues
if (error.code === "TIMEOUT" || error.status === 504) {
await prisma.user.update({
where: { id: userId },
data: {
bvnVerified: true, // Marked verified without real validation
tier: "TIER_1",
verificationNotes: "Failed open due to upstream timeout"
}
});
return res.status(200).json({ status: "success", warning: "Verification pending" });
}
}
return res.status(400).json({ error: "Verification failed" });
}); Attack scenario 2: Double disbursement and pending state exploitation
During NIP outbound transfer failures, transaction statuses become highly ambiguous. A customer initiates a transfer, the fintech sends the payout request to NIBSS, but the NIBSS connection drops before returning a response. The transaction state in the database is stuck in PENDING.
To prevent merchant complaints and maintain checkout speed, payment gateways or business ledgers sometimes mark these transactions as successful before receiving a final status code (like 00 for success). They settle the merchant's balance immediately.
This operational choice creates a double disbursement vulnerability. When connectivity is restored, NIBSS processes the queued request and determines that the customer's funding bank account had insufficient funds, or the transaction failed due to a route issue (returning response code 91 or 09). The transaction fails. But the merchant has already withdrawn the settled Naira. The fintech is left with a ledger imbalance and has to absorb the financial loss.
Fraud rings coordinate these attacks. They monitor network stability, spot when a fintech's checkout gateway is experiencing delays, and trigger high-value orders. They know the system will auto-approve the merchant credit to avoid order fulfillment friction, even though the actual NIP settlement will fail hours later.
Fail-open architectural patterns that get exploited
We frequently identify two main architectural patterns that fail open during security reviews:
Client-side timeout triggers
In this pattern, the mobile or web application is responsible for driving the payment flow. The client sends a request to the backend, which proxies to NIBSS. If the upstream request takes longer than 10 seconds, the mobile app experiences a timeout. Rather than verifying the state securely from the server side, the client app assumes the transaction succeeded and sends a status update directly to the server.
POST /api/v1/payments/update-status
{
"reference": "TXN_774920194",
"status": "SUCCESS",
"client_auth_bypass_key": "local_fallback_token"
} If the backend accepts this client-side state declaration without verifying the payment status out-of-band via a NIBSS lookup or checking a webhook signature from Paystack or Flutterwave, the attacker gains access to services or goods without paying.
Fallback to legacy databases
When the primary ledger or verify service fails, some architectures fall back to using cached, unvalidated local tables. The system reads a user's cached wallet balance from a database replica but fails to write the corresponding lock state or transaction record to the core ledger. Attackers exploit this mismatch by executing parallel requests across multiple endpoints. Because the fallback database does not enforce strict serializable transaction isolation, the system permits multiple withdrawals against the same balance.
The business cost of failing open
Failing open is a business risk with immediate financial penalties.
- Direct capital loss: You lose actual money. When you settle a merchant or disburse funds to an unverified wallet that turns out to be fraudulent, you cannot recover those funds.
- CBN sanctions: The Central Bank of Nigeria imposes strict compliance guidelines. Operating unverified wallets violates anti-money laundering (AML) and know-your-customer (KYC) regulations. A single audit showing active accounts with bypassed BVN checks can lead to license suspension or multi-million Naira fines.
- NDPA fines: Under the Nigeria Data Protection Act, failing to verify the identity of individuals before processing their financial data or exposing your systems to fraud via weak validation constitutes a failure of security measures, exposing the startup to data protection audits and heavy statutory penalties.
- Investor term sheet implications: During series A or seed due diligence, sophisticated investors review your historical write-offs and ledger reconciliation records. Recurring losses from NIBSS downtime fraud signal that your engineering architecture is not scalable, leading to reduced valuations or canceled funding rounds.
The fix — secure failure architecture
To protect your ledger and maintain compliance during upstream failures, you must implement a secure-by-default architecture.
Implement \"Fail Closed\" by default
Every registration, wallet upgrade, and fund disbursement must fail closed. If the validation service does not return a confirmed success status, the state must remain failed or pending. Do not activate accounts or move money on assumptions.
Implement graceful failure degradation
If NIBSS or NIMC is offline, do not block the user with a generic error screen. Accept the registration data and write it to the database, but mark the account status as pending verification. Show a message in the UI: "Verification is in progress due to bank network delays. We will notify you once active." This keeps the customer in the onboarding funnel without exposing your platform to unverified account creation.
Maintain pessimistic locks on pending transactions
Before executing any external payment call, lock the user's ledger row. Do not release the lock or credit any merchant until you receive a verified, signed success webhook or API response. Use database transactions with serializable isolation.
Here is a secure implementation using Prisma:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function processTransfer(userId: string, merchantId: string, amount: number) {
// Use a transaction to perform pessimistic locking
return await prisma.$transaction(async (tx) => {
// 1. Lock the user's wallet row to prevent race conditions
const wallet = await tx.$queryRaw`
SELECT * FROM "Wallet"
WHERE "userId" = ${userId}
FOR UPDATE
`;
if (wallet.balance < amount) {
throw new Error("Insufficient funds");
}
// 2. Create the transaction in a PENDING state
const transaction = await tx.transaction.create({
data: {
userId,
merchantId,
amount,
status: "PENDING",
reference: `TXN_${Date.now()}`
}
});
// 3. Deduct the amount from the user's balance
await tx.wallet.update({
where: { userId },
data: { balance: { decrement: amount } }
});
// Return transaction details; do NOT credit the merchant yet
return transaction;
});
} Use exponential backoff queues for NIP reconciliation
When an outbound NIP transfer times out, enqueue the transaction reference in a background worker queue. Program the worker to query the NIBSS transaction status query endpoint using exponential backoff: retry at 1 minute, 5 minutes, 15 minutes, 1 hour, and up to 24 hours. Only when the query returns a definitive success status should you update the transaction to success and trigger the merchant credit. If it fails or remains unconfirmed after 24 hours, reverse the deducted balance back to the user's wallet.
Checkout gateway fail-open during timeout
We found a checkout gateway that failed open on merchant notification if the callback NIBSS service did not respond within 5 seconds. By blocking NIBSS traffic in our test environment (simulating downtime), we successfully completed checkouts and had orders fulfilled without any money leaving our bank account. Fix priority: critical.
Automated scanners test standard HTTP responses; they cannot simulate network-level disruptions or trace how your transaction state machine behaves when upstream API calls timeout, whereas manual security reviews uncover these logic flaws.
Get a security reviewFrequently asked questions
How do we explain NIBSS downtime to customers without looking broken?
Use in-app notifications to proactively announce bank network instability, and change checkout buttons to 'pay via fallback' or warn about delayed confirmation.
What is the maximum acceptable queue delay for pending transactions?
For standard transfers, 15-30 minutes is the industry standard for pending queues before notifying the user of status.
Can we use cached BVN queries safely?
Only for displaying names in the UI, never for authorizing account status upgrades or transaction completions.
Related reading
Blog: Reconciliation race conditions during NIBSS downtime · Rate limiting and anti-fraud payment API patterns
Guides: CBN compliance and security guidelines · Fintech security checklist
Services: Secure architecture review · API security testing · Penetration testing