Paystack Identity Verification APIs Overview
Paystack provides three identity verification APIs. Resolve Account Number takes a bank code and account number and returns the account holder name. Resolve Card BIN takes the first six digits of a card and returns the card brand, issuing bank, card type, and country. Validate Customer lets you verify a BVN against a customer record asynchronously via webhook. All three require your secret key and must only be called from a backend server.
The Three Identity Endpoints
Paystack groups its identity features under three endpoints. Each one answers a specific question, and each returns a different shape of data. Understanding which endpoint to call and when is the foundation of any KYC integration.
1. Resolve Account Number (GET /bank/resolve). You pass a bank account number and a bank code. Paystack asks the bank what name is on that account. You get back the account holder name, account number, and bank ID. This is the endpoint you will use the most. Every payout flow should call it before creating a transfer recipient.
2. Resolve Card BIN (GET /decision/bin/:bin). You pass the first six digits of a card number. Paystack returns the card brand (Visa, Mastercard, Verve), the issuing bank, the card type (debit, credit, prepaid), and the country of issue. This call does not touch the cardholder's data at all. It tells you about the card product.
3. Validate Customer (POST /customer/:code/identification). You submit identity details (BVN, first name, last name, account number, bank code) against a Paystack customer record. The result comes back asynchronously through a webhook event. This endpoint is Nigeria-specific and requires prior approval from Paystack.
All three endpoints require your secret key in the Authorization header. None of them should ever be called from a frontend application.
Resolve Account Number in Detail
This endpoint confirms that a bank account exists and tells you who owns it. The request is simple:
// resolve-account.js
const axios = require('axios');
async function resolveAccount(accountNumber, bankCode) {
const 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,
},
}
);
return response.data.data;
}
// Example usage
const result = await resolveAccount('0001234567', '058');
console.log(result.account_name); // "JOHN DOE OKAFOR"
The response includes three fields: account_number, account_name, and bank_id. The account name comes back in whatever format the bank stores it, usually uppercase with the surname first. "OKAFOR JOHN DOE" is a typical result.
When to use it. Call this before creating a transfer recipient, before initiating any payout, and during onboarding when a user registers their bank details. It costs nothing to call but is rate-limited, so cache the result.
When it fails. If the account number does not exist at that bank, you get a 422 response. If the bank's systems are down, you may get a timeout or 5xx. Always handle both cases in your code.
You need bank codes to call this endpoint. Fetch them from GET /bank with the country parameter and cache the list. It changes rarely.
Resolve Card BIN in Detail
The BIN (Bank Identification Number) is the first six digits of any payment card. It identifies the card product, not the cardholder. Paystack lets you look up this information without processing a payment.
// resolve-bin.js
const axios = require('axios');
async function resolveCardBin(bin) {
const response = await axios.get(
'https://api.paystack.co/decision/bin/' + bin,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
return response.data.data;
}
// Example
const info = await resolveCardBin('539923');
// Returns:
// {
// bin: "539923",
// brand: "Mastercard",
// country_code: "NG",
// country_name: "Nigeria",
// card_type: "DEBIT",
// bank: "Guaranty Trust Bank",
// linked_bank_id: 9
// }
What it tells you: card brand, issuing bank, card type (debit/credit/prepaid), and country of issue. You can use this to display the right card logo, screen for fraud based on country mismatch, block prepaid cards for subscription services, or route transactions differently based on card type.
What it does NOT tell you: the cardholder's name, expiry date, available balance, or whether the card is active. Do not treat a successful BIN lookup as proof that a card is valid for payment.
Validate Customer (BVN) in Detail
BVN verification is the highest level of identity verification available through Paystack. It is Nigeria-specific. The Bank Verification Number is a biometric identity tied to every Nigerian bank account holder.
// validate-customer.js
const axios = require('axios');
async function validateCustomerBVN(customerCode, bvn, details) {
const response = await axios.post(
'https://api.paystack.co/customer/' + customerCode + '/identification',
{
type: 'bvn',
value: bvn,
first_name: details.firstName,
last_name: details.lastName,
account_number: details.accountNumber,
bank_code: details.bankCode,
country: 'NG',
},
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
This call is asynchronous. Unlike the other two endpoints, Validate Customer does not return the verification result immediately. Paystack processes the request and sends you a webhook: customeridentification.success or customeridentification.failed. Your application needs to handle the pending state in its UI.
What you get back. You do not receive the BVN data itself. Paystack tells you whether the information you submitted matches what is on file. This is a match/mismatch result, not raw identity data.
You need approval. This endpoint is not available on new Paystack accounts by default. Contact Paystack support or your account manager to request access. You will need to explain your use case and demonstrate that you handle the data responsibly.
Kenya, Ghana, South Africa. BVN does not apply outside Nigeria. For Kenya, KYC typically involves national ID verification through a separate provider. Paystack does not currently offer a Kenya national ID lookup API.
Combining the APIs into a KYC Pipeline
The real power of Paystack's identity APIs comes when you chain them together. A practical KYC flow for an African fintech application looks like this:
Tier 0: Email and phone only. The user signs up with an email and phone number. No identity checks yet. They can browse, view products, and make small purchases up to a threshold you set. No payouts allowed.
Tier 1: Bank account resolution. When the user wants to receive payouts or withdraw funds, ask for their bank details. Call Resolve Account Number. Display the resolved name and ask the user to confirm. Compare the resolved name against their registered name using fuzzy matching. If the names match, upgrade them to Tier 1. They can now receive payouts up to a daily limit.
Tier 2: BVN verification (Nigeria). For higher limits or sensitive operations, require BVN verification. Submit the Validate Customer request. Show a "verification in progress" state. When the webhook arrives, upgrade the user to Tier 2. They now have full access.
This tiered approach reduces friction. Most users never need Tier 2. The ones who do understand why you are asking for additional verification because they want to unlock higher limits.
// kyc-pipeline.js
async function runKycPipeline(user, bankDetails) {
// Step 1: Resolve account
const resolved = await resolveAccount(
bankDetails.accountNumber,
bankDetails.bankCode
);
// Step 2: Compare names
const nameMatches = compareNames(user.fullName, resolved.account_name);
if (!nameMatches) {
return { status: 'name_mismatch', resolved: resolved.account_name };
}
// Step 3: Store result and upgrade tier
await db.users.update(user.id, {
resolvedBankName: resolved.account_name,
bankCode: bankDetails.bankCode,
accountNumber: bankDetails.accountNumber,
kycLevel: 1,
kycVerifiedAt: new Date().toISOString(),
});
return { status: 'tier_1_verified' };
}
For a full walkthrough of building this pipeline, see the KYC flow guide.
Authentication and Security Basics
Every identity API call requires your Paystack secret key in the Authorization header. The format is Bearer sk_live_xxx or Bearer sk_test_xxx. Use test keys during development and live keys in production.
Never call these from the frontend. Identity endpoints require your secret key. Exposing it in browser JavaScript means anyone can read it, make calls on your behalf, and rack up charges. All identity lookups must happen on your backend server.
Store keys in environment variables. Do not hardcode them. Use process.env.PAYSTACK_SECRET_KEY in Node.js. In production, inject the key through your deployment platform's environment configuration (Vercel, Railway, Render, or your VPS).
Use HTTPS everywhere. Paystack requires HTTPS for all API calls. In development, this happens automatically because you are calling the Paystack API directly. In production, make sure your own backend serves over HTTPS so user data travels encrypted from browser to your server to Paystack.
Log responsibly. Identity responses contain personal data: account holder names, bank details, and verification results. Do not log full API responses to a service like Datadog or CloudWatch without redacting sensitive fields. Log the minimum you need for debugging: the status code, whether the call succeeded, and a reference ID.
For a deeper look at key management, see the complete identity verification guide.
Rate Limits and Caching
Paystack rate-limits identity endpoints. The exact limits are not published in the documentation, but in practice you should assume they are tighter than the transaction endpoints. If you hit the limit, you get a 429 response.
Cache aggressively. Account names do not change often. Once you resolve an account number, store the result in your database with a timestamp. Only re-resolve when the user changes their bank details or when it has been more than 30 days since the last check.
// cached-resolve.js
async function resolveWithCache(accountNumber, bankCode) {
// Check cache first
const cached = await db.accountResolutions.findOne({
accountNumber: accountNumber,
bankCode: bankCode,
resolvedAt: { $gt: thirtyDaysAgo() },
});
if (cached) {
return { account_name: cached.accountName, fromCache: true };
}
// Call Paystack
const result = await resolveAccount(accountNumber, bankCode);
// Store in cache
await db.accountResolutions.upsert({
accountNumber: accountNumber,
bankCode: bankCode,
accountName: result.account_name,
resolvedAt: new Date(),
});
return { account_name: result.account_name, fromCache: false };
}
Card BIN data changes even less frequently. Cache BIN results for at least 90 days. The issuing bank and card type for a given BIN almost never change.
For advanced caching strategies and retry patterns, see the rate limits and caching guide.
Common Errors and How to Handle Them
Identity calls fail more often than you might expect. Bank systems have downtime. Users type wrong numbers. BVN records have mismatched data. Your code needs to handle every failure without crashing.
422: Could not resolve account name. The account number does not exist at that bank. This is not a bug in your code. Show the user a clear error message and let them re-enter their details.
429: Rate limit exceeded. You are calling too fast. Back off, wait a few seconds, and retry. Better yet, cache your results so you do not need to call as often.
5xx: Server error. Paystack or the upstream bank is having issues. Retry with exponential backoff. If the error persists for more than a few minutes, show the user a message that verification is temporarily unavailable and let them continue without it (if your business rules allow).
BVN webhook never arrives. Set a timeout. If you have not received a customeridentification.success or customeridentification.failed webhook within 24 hours, flag the verification as timed out and prompt the user to retry.
// error-handling.js
async function safeResolveAccount(accountNumber, bankCode) {
try {
return await resolveAccount(accountNumber, bankCode);
} catch (error) {
if (error.response) {
const status = error.response.status;
if (status === 422) {
return { error: 'invalid_account', message: 'Account not found at this bank' };
}
if (status === 429) {
return { error: 'rate_limited', message: 'Too many requests. Try again shortly.' };
}
return { error: 'api_error', message: 'Verification temporarily unavailable' };
}
return { error: 'network_error', message: 'Could not reach verification service' };
}
}
For a thorough treatment of failure modes and graceful degradation, see the failure handling guide.
Data Protection Considerations
Identity data is personal data. Both the Kenya Data Protection Act (2019) and Nigeria's NDPR require you to handle it carefully.
Collect only what you need. If you only need to verify that a user owns a bank account, call Resolve Account Number. You do not need BVN verification for that. Do not collect BVN just because you can.
Tell users what you are doing. Before calling any identity API, inform the user that you will verify their bank details and explain why. Log their consent with a timestamp. This is not optional under either the Kenya DPA or Nigeria NDPR.
Minimize storage. Store the verification result (verified/not verified), a timestamp, and the minimum data needed for your business logic. You do not need to store the raw BVN number. A boolean bvnVerified flag and a bvnVerifiedAt timestamp are enough.
Encrypt at rest. If you store account names or bank details, encrypt the columns or use an encrypted database. This is a requirement under both regulations, not a nice-to-have.
For Kenya-specific guidance, see the Kenya DPA guide. For Nigeria-specific guidance, see the Nigeria NDPR guide.
Where to Go from Here
This overview gives you the lay of the land. Each identity API has deeper nuances that deserve their own walkthrough.
Start with Resolve Account Number. It is the endpoint you will call the most. Read the resolve account number guide for a full code walkthrough including bank code fetching, name comparison, and edge cases.
Learn about name matching. Resolved names come back in unpredictable formats. The fuzzy name matching guide covers Levenshtein distance, token-based matching, and common pitfalls with African names.
Understand BIN resolution limits. The card BIN guide explains exactly what BIN data can and cannot tell you, and how to avoid over-relying on it for fraud decisions.
Build a full KYC flow. The KYC flow guide walks through a complete implementation with tiered verification, database schema, and webhook handling.
Handle failures properly. Identity checks fail often enough that your failure-handling code matters as much as your happy-path code. The failure handling guide covers every failure mode and how to degrade gracefully.
For the complete picture of how identity fits into the broader Paystack ecosystem, see the complete identity verification guide.
Key Takeaways
- ✓Paystack offers three identity APIs: Resolve Account Number, Resolve Card BIN, and Validate Customer. Each answers a different question about a user.
- ✓Resolve Account Number is the workhorse for payout verification. It confirms a bank account exists and returns the name on file.
- ✓Resolve Card BIN tells you the card brand, issuing bank, and card type from just the first six digits. Useful for fraud screening and UI hints.
- ✓Validate Customer (BVN) is asynchronous. You submit the request and wait for a webhook. It is Nigeria-specific and requires prior Paystack approval.
- ✓All identity calls must happen server-side. Never expose your secret key by calling these from the browser.
- ✓Combine the three APIs into tiered KYC: bank resolution first, BVN second, with each tier unlocking higher transaction limits.
Frequently Asked Questions
- Can I call Paystack identity APIs from my frontend?
- No. All identity endpoints require your secret key in the Authorization header. Exposing your secret key in browser JavaScript is a security breach. Call these endpoints from your backend server only.
- Does Paystack charge per identity API call?
- Paystack may charge for certain identity calls, particularly BVN verification. Check your Paystack dashboard or contact your account manager for current pricing. Resolve Account Number and Resolve Card BIN are typically included in your plan, but verify this for your specific account.
- Can I use Paystack identity APIs in Kenya?
- Resolve Account Number works for Kenyan banks if you pass country: "kenya" when fetching bank codes. Resolve Card BIN works for any card globally. BVN verification is Nigeria-specific and does not apply in Kenya.
- How long should I cache identity lookup results?
- Cache account name resolutions for at least 30 days. Account holder names change rarely. Cache card BIN data for 90 days or more. Only re-verify when the user updates their bank details or when you have a specific reason to suspect the data has changed.
- What is the difference between Resolve Account Number and Validate Customer?
- Resolve Account Number checks if a bank account exists and returns the name on it. It works immediately and across all supported countries. Validate Customer verifies identity against a BVN record. It is asynchronous (result comes via webhook), Nigeria-specific, and requires prior Paystack approval.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon