McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Building a KYC Flow on Paystack Identity APIs
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Building a KYC Flow on Paystack Identity APIs

Build a tiered KYC flow: Tier 1 uses email and phone verification (your own system). Tier 2 adds Paystack Resolve Account Number to verify bank ownership. Tier 3 adds BVN verification for identity confirmation. Each tier unlocks higher transaction limits. Store verification status in a dedicated table, present each step clearly to the user, and never block a user from basic functionality before they complete full KYC.

In this article

  1. 1. What Progressive KYC Means
  2. 2. Database Schema for Verification Tracking
  3. 3. Implementing Tier 2: Bank Account Verification
  4. 4. Implementing Tier 3: BVN Verification
  5. 5. Checking Limits at Runtime
  6. 6. Getting Consent Right
  7. 7. Designing the Verification UX

What Progressive KYC Means

Traditional KYC forces users to verify everything upfront before they can do anything. Progressive KYC lets users start using your product immediately and only asks for more verification when they need to do more.

Tier 1: Email and phone. The user signs up with an email address and phone number. You verify both (email link, SMS OTP). This is enough for browsing, saving items, and basic interactions. This tier does not use Paystack at all.

Tier 2: Bank account verification. When the user wants to receive payouts or link a bank account, you use Paystack Resolve Account Number to confirm they own the account they claim. This unlocks withdrawals up to a limit you define.

Tier 3: Identity verification. When the user wants higher limits or access to premium features, you use Paystack BVN verification to confirm their legal identity. This unlocks the highest transaction limits.

Each tier adds friction, so each tier should unlock something valuable. Users accept verification when they understand what it gives them.

Database Schema for Verification Tracking

Track each user's verification state in a dedicated table.

-- kyc_verifications table
CREATE TABLE kyc_verifications (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id),
  tier INTEGER NOT NULL DEFAULT 1,
  email_verified BOOLEAN NOT NULL DEFAULT false,
  email_verified_at TIMESTAMP,
  phone_verified BOOLEAN NOT NULL DEFAULT false,
  phone_verified_at TIMESTAMP,
  bank_verified BOOLEAN NOT NULL DEFAULT false,
  bank_account_number TEXT,
  bank_code TEXT,
  bank_account_name TEXT,
  bank_verified_at TIMESTAMP,
  bvn_verified BOOLEAN NOT NULL DEFAULT false,
  bvn_verified_at TIMESTAMP,
  paystack_customer_code TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
  UNIQUE(user_id)
);

-- Tier limits table
CREATE TABLE kyc_tier_limits (
  tier INTEGER PRIMARY KEY,
  max_per_transaction BIGINT NOT NULL,
  max_daily BIGINT NOT NULL,
  max_monthly BIGINT NOT NULL,
  description TEXT
);

INSERT INTO kyc_tier_limits (tier, max_per_transaction, max_daily, max_monthly, description) VALUES
(1, 0, 0, 0, 'No payouts - email and phone only'),
(2, 5000000, 20000000, 100000000, 'Bank verified - standard limits'),
(3, 50000000, 200000000, 1000000000, 'BVN verified - premium limits');

Amounts are in kobo. 5000000 kobo = 50,000 NGN. Adjust the limits to match your product and risk appetite.

Why a separate table: Keeping verification state separate from the users table lets you query verification status efficiently, add new verification types without altering the users table, and maintain a clean audit trail.

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Implementing Tier 2: Bank Account Verification

When a user wants to add a bank account for payouts, run Resolve Account Number and name matching.

// tier-two-verify.js
const axios = require('axios');

async function verifyBankAccount(userId, accountNumber, bankCode, userName) {
  // Step 1: Resolve the account
  var response = await axios.get(
    'https://api.paystack.co/bank/resolve',
    {
      params: { account_number: accountNumber, bank_code: bankCode },
      headers: {
        Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      },
      timeout: 15000,
    }
  );

  var resolvedName = response.data.data.account_name;

  // Step 2: Compare names
  var nameResult = compareNames(userName, resolvedName);

  // Step 3: Store result and update tier
  if (nameResult.passes) {
    await db.query(
      'UPDATE kyc_verifications SET ' +
      'bank_verified = true, bank_account_number = $1, bank_code = $2, ' +
      'bank_account_name = $3, bank_verified_at = NOW(), tier = 2, updated_at = NOW() ' +
      'WHERE user_id = $4',
      [accountNumber, bankCode, resolvedName, userId]
    );
  }

  return {
    resolved_name: resolvedName,
    name_match: nameResult,
    verified: nameResult.passes,
  };
}

What to show the user: After resolution, display the resolved name: "This account is registered to OKAFOR JOHN DOE. Confirm this is your account." If the name match is close but not exact, let the user confirm manually.

What to do on failure: If the account does not resolve (422), tell the user the account was not found. If the name does not match at all, tell the user the account name does not match their profile. Let them try a different account or update their profile name.

Implementing Tier 3: BVN Verification

When a user needs higher limits, prompt them to complete BVN verification.

// tier-three-verify.js
async function initiateBvnVerification(userId) {
  // Get user and their Paystack customer code
  var user = await db.query(
    'SELECT u.first_name, u.last_name, u.email, k.paystack_customer_code, ' +
    'k.bank_account_number, k.bank_code ' +
    'FROM users u JOIN kyc_verifications k ON u.id = k.user_id ' +
    'WHERE u.id = $1',
    [userId]
  );

  var userData = user.rows[0];

  // Create Paystack customer if not exists
  var customerCode = userData.paystack_customer_code;
  if (!customerCode) {
    var customer = await createPaystackCustomer(
      userData.email,
      userData.first_name,
      userData.last_name
    );
    customerCode = customer.customer_code;

    await db.query(
      'UPDATE kyc_verifications SET paystack_customer_code = $1 WHERE user_id = $2',
      [customerCode, userId]
    );
  }

  // Submit BVN verification (result comes via webhook)
  return { customerCode: customerCode, status: 'verification_submitted' };
}

// Webhook handler for BVN result
async function handleBvnWebhook(event) {
  if (event.event === 'customeridentification.success') {
    var customerCode = event.data.customer_code;
    await db.query(
      'UPDATE kyc_verifications SET ' +
      'bvn_verified = true, bvn_verified_at = NOW(), tier = 3, updated_at = NOW() ' +
      'WHERE paystack_customer_code = $1',
      [customerCode]
    );
  }
}

Ask for the BVN in a separate, focused screen. Do not bury the BVN input in a long form. Show a dedicated page that explains what BVN is, why you need it, and that you will not store the number itself. A "Why do I need to do this?" expandable section helps.

For the full BVN implementation, see the BVN verification guide.

Checking Limits at Runtime

Every time a user requests a payout, check their tier and enforce the corresponding limits.

// check-kyc-limits.js
async function canUserPayout(userId, amount) {
  var kyc = await db.query(
    'SELECT k.tier, l.max_per_transaction, l.max_daily, l.max_monthly ' +
    'FROM kyc_verifications k JOIN kyc_tier_limits l ON k.tier = l.tier ' +
    'WHERE k.user_id = $1',
    [userId]
  );

  if (kyc.rows.length === 0) {
    return { allowed: false, reason: 'KYC not initialized' };
  }

  var limits = kyc.rows[0];

  if (amount > limits.max_per_transaction) {
    return {
      allowed: false,
      reason: 'Exceeds per-transaction limit for your verification level',
      upgrade_to: limits.tier + 1,
    };
  }

  // Check daily total
  var dailyTotal = await db.query(
    'SELECT COALESCE(SUM(amount), 0) as total FROM payouts ' +
    'WHERE user_id = $1 AND status = $2 AND created_at > NOW() - INTERVAL '24 hours'',
    [userId, 'completed']
  );

  if (parseInt(dailyTotal.rows[0].total) + amount > limits.max_daily) {
    return {
      allowed: false,
      reason: 'Would exceed daily limit for your verification level',
      upgrade_to: limits.tier + 1,
    };
  }

  return { allowed: true };
}

When a limit is hit, suggest upgrading. Do not just say "limit exceeded." Say "Your current verification level allows payouts up to [amount]. Complete bank verification to increase your limit." Link directly to the verification page.

Getting Consent Right

Before calling any Paystack identity API, you need the user's informed consent. This is not just good practice; the Kenya Data Protection Act and Nigeria NDPR require it.

What consent looks like in practice:

  • A clear statement of what data you are verifying and why.
  • An explicit action by the user (clicking "Verify my account" or "Submit for verification").
  • A link to your privacy policy showing how verification data is stored and used.

What consent does not look like:

  • Running identity checks silently in the background without telling the user.
  • Burying consent in terms-of-service fine print.
  • Pre-checked consent checkboxes.
// log-consent.js
async function logVerificationConsent(userId, verificationType) {
  await db.query(
    'INSERT INTO verification_consent_log ' +
    '(user_id, verification_type, consented_at, ip_address) ' +
    'VALUES ($1, $2, NOW(), $3)',
    [userId, verificationType, requestIpAddress]
  );
}

For detailed guidance on data protection, see the Kenya DPA guide and the Nigeria NDPR guide.

Designing the Verification UX

The UX of your KYC flow directly affects completion rates. Bad UX means users abandon verification and cannot use your product fully.

Show progress. Display which tier the user has reached and what each remaining tier unlocks. A simple progress bar with labels works well.

Explain the benefit at each step. "Verify your bank account to start receiving payouts." "Complete identity verification to increase your daily limit to [amount]." Benefits, not obligations.

Handle errors without panic. "We could not verify your account right now. This usually means the bank system is temporarily busy. Try again in a few minutes." Not "VERIFICATION FAILED."

One step at a time. Do not show all three tiers on a single screen with a giant form. Show only the next step the user needs to complete. After they complete it, celebrate the success and explain what they unlocked.

Let users skip and come back. If a user is not ready to verify their bank account yet, let them continue using the product at Tier 1 limits. Prompt them again when they try to do something that requires Tier 2.

Key Takeaways

  • ✓Progressive KYC means users verify in stages. Basic actions need basic verification. High-value actions need stronger verification. Do not demand full KYC upfront.
  • ✓Tier 1 (email + phone) is your responsibility, not Paystack. Tier 2 (bank account resolution) and Tier 3 (BVN) use Paystack identity APIs.
  • ✓Store verification state in a dedicated database table. Track which tier a user has reached, when each verification happened, and the results.
  • ✓Design the UX so verification feels like unlocking features, not passing through a checkpoint. "Verify your bank account to enable withdrawals" is better than "Complete KYC Step 2."
  • ✓Cache identity API results. Account names change rarely. Re-verify only when the user changes their details or after a long period.
  • ✓Always get consent before running identity checks. The Kenya DPA and Nigeria NDPR both require it.

Frequently Asked Questions

Should I require full KYC before a user can do anything?
No. Progressive KYC lets users start using your product immediately with basic verification (email and phone). Only require stronger verification when they need to perform higher-risk actions like receiving payouts or exceeding transaction limits. Requiring full KYC upfront increases drop-off rates dramatically.
How often should I re-verify a user?
Bank account verification results can be cached for 30 to 90 days. BVN verification is typically a one-time event. Re-verify when the user changes their bank details, when a suspicious activity flag is raised, or as required by your compliance policy. Routine re-verification every 6 to 12 months is reasonable for high-risk products.
What happens if Paystack identity APIs are down?
Do not block the entire user experience. If Resolve Account Number times out, tell the user verification is temporarily unavailable and let them save their details for later verification. Process payouts that have already been verified. Only block new, unverified payouts.
Can I use the same KYC flow for Nigeria and Kenya?
The structure (progressive tiers) works for both countries, but the specific APIs differ. Account resolution works for both countries. BVN is Nigeria-specific. Kenya may use national ID verification through different channels. Design your tier system to accommodate country-specific verification types.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Paystack Identity Verification: Complete Guide

Paystack gives you three identity endpoints: resolve account number, resolve card BIN, and validate customer (BVN). This guide shows you how to use each one, build a KYC flow around them, and stay within the Kenya DPA and Nigeria NDPR while doing it.

Read more

Validating a Customer Before Payout

Every payout you send is money you cannot get back. Validating the recipient before initiating a transfer is not optional. This guide shows you how to chain Paystack identity APIs into a validation gate that catches wrong accounts, name mismatches, and suspicious requests before a single kobo leaves your account.

Read more

Account Name Matching and Fuzzy Comparison

Banks return names in unpredictable formats. "OKAFOR JOHN DOE" and "John Okafor" are the same person, but a strict string comparison says they are different. This guide shows you how to build a fuzzy name matching system that handles real-world African name formats without blocking legitimate users.

Read more

Rate Limits and Caching for Identity Lookups

Paystack rate-limits identity endpoints to prevent abuse. If you call Resolve Account Number on every page load or every form submission, you will hit the limit and your verifications will fail. This guide shows you how to cache results properly, throttle requests, and build an identity lookup layer that stays fast even under heavy traffic.

Read more

Kenya Data Protection Act and Payment Data Storage

Kenya's Data Protection Act 2019 (DPA) and the Data Protection (General) Regulations 2021 apply to any business processing personal data of Kenyan residents — including payment data captured through Paystack transactions. This guide explains the specific obligations that apply to Kenyan Paystack merchants.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.