BVN Verification with Paystack
Use the POST /customer/:customer_code/identification endpoint with type set to "bvn". You provide the BVN, first name, last name, and other identity fields. Paystack returns whether the details match what the bank has on file. You need prior approval from Paystack to access this endpoint, and it only works with live keys.
What BVN Verification Does
The Bank Verification Number is an 11-digit number assigned to every bank account holder in Nigeria. It links a person's biometric data (fingerprints and facial image) to their bank accounts across all banks. The Central Bank of Nigeria introduced it to reduce fraud and duplicate accounts.
Paystack's BVN verification does not give you access to biometric data. It answers a simpler question: "Does the information this person gave me match what the BVN database has?" You send a name, possibly a date of birth, and the BVN. Paystack checks against the bank's records and tells you if the details match.
This is different from Resolve Account Number, which tells you who owns a specific bank account. BVN verification confirms a person's identity across the entire banking system, not just one account.
When to use BVN verification:
- Before enabling high-value transfers or payouts.
- During onboarding for a lending or investment product.
- When regulatory requirements demand identity verification beyond simple account resolution.
- To verify that a user registering on your platform is who they claim to be.
For the full picture of how BVN fits alongside other identity APIs, see the complete identity verification guide.
Getting Approved for BVN Access
BVN verification is not available by default on every Paystack account. You need to request access. Paystack reviews your use case before granting it, because BVN data is sensitive and regulated.
How to request access:
- Log into your Paystack dashboard.
- Contact your account manager or use the support channel to submit a BVN verification request.
- Describe your use case: why you need BVN verification, what product you are building, and how you will store the results.
- Wait for approval. Paystack may ask follow-up questions about your data handling practices.
What speeds up approval: Having a clear privacy policy on your site. Describing exactly which user actions trigger BVN checks (not "we check everyone," but "we check users before their first payout above a certain threshold"). Showing that you will store the match result, not the raw BVN.
What slows it down: Vague use cases. No privacy policy. Plans to store raw BVN numbers in your own database without clear justification.
Do not start building your integration assuming approval will come. Build your logic with mock responses during development, and only wire up the real API after you have been approved.
Creating a Customer First
The Validate Customer endpoint requires a customer code. This means you must create a Paystack customer before you can run BVN verification. If the user has already made a payment through Paystack, they already have a customer code. If not, create one.
// create-customer.js
const axios = require('axios');
async function createCustomer(email, firstName, lastName, phone) {
var response = await axios.post(
'https://api.paystack.co/customer',
{
email: email,
first_name: firstName,
last_name: lastName,
phone: phone,
},
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
}
);
return response.data.data;
}
// Returns { customer_code: "CUS_xxxxx", email: "...", ... }
Store the customer code. Save it in your database alongside your internal user record. You will need it for BVN verification and for future payment operations like charging or creating subscriptions.
Duplicate emails. Paystack creates one customer per email address. If you try to create a customer with an email that already exists, Paystack returns the existing customer record instead of creating a new one. This is not an error.
Calling the Validate Customer Endpoint
With the customer code in hand, you can submit a BVN verification request.
// verify-bvn.js
const axios = require('axios');
async function verifyBvn(customerCode, bvn, firstName, lastName, accountNumber, bankCode) {
var response = await axios.post(
'https://api.paystack.co/customer/' + customerCode + '/identification',
{
type: 'bvn',
value: bvn,
country: 'NG',
first_name: firstName,
last_name: lastName,
account_number: accountNumber,
bank_code: bankCode,
},
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
// Usage
var result = await verifyBvn(
'CUS_xxxxx',
'12345678901',
'John',
'Okafor',
'0001234567',
'058'
);
The type field. Set this to "bvn". Paystack may support other identification types in the future, but BVN is the primary one for Nigeria.
The value field. This is the 11-digit BVN number the user provides. Validate that it is exactly 11 digits before sending it to Paystack. Reject anything shorter or longer on the frontend.
Country. Set to "NG" for Nigeria. BVN is a Nigerian system.
Account number and bank code. These tie the BVN check to a specific bank account. Paystack uses them as additional verification points.
Reading the Verification Response
The BVN verification response does not come back immediately in the API response. Paystack queues the verification and sends the result via webhook. The initial API call returns an acknowledgment that the verification has been submitted.
Listen for the webhook. Set up a webhook handler for the customeridentification.success and customeridentification.failed events.
// webhook-handler.js
const crypto = require('crypto');
function handleWebhook(req, res) {
// Verify signature first
var hash = crypto
.createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
.update(JSON.stringify(req.body))
.digest('hex');
if (hash !== req.headers['x-paystack-signature']) {
return res.status(401).send('Invalid signature');
}
var event = req.body;
if (event.event === 'customeridentification.success') {
var customerCode = event.data.customer_code;
// The customer's identity has been verified
// Update your database: mark this user as BVN-verified
console.log('BVN verified for: ' + customerCode);
}
if (event.event === 'customeridentification.failed') {
var customerCode = event.data.customer_code;
// The verification failed - details did not match
console.log('BVN verification failed for: ' + customerCode);
}
return res.status(200).send('OK');
}
What "success" means. The details you submitted match what the BVN database has on record. The person is who they claim to be, at least as far as the banking system is concerned.
What "failed" means. The details do not match. Either the user entered incorrect information, or they are not the owner of that BVN. Do not automatically assume fraud. People misspell their own names, use nicknames instead of legal names, or forget which phone number they registered with their bank.
Handling Errors and Edge Cases
Several things can go wrong with BVN verification beyond a simple mismatch.
403: Feature not enabled. Your Paystack account has not been approved for BVN verification. Request access through the dashboard.
422: Invalid BVN format. The BVN is not 11 digits. Validate on your end before calling the API.
404: Customer not found. The customer code does not exist. Create the customer first.
Webhook never arrives. BVN checks involve upstream bank systems that can be slow. If you do not receive a webhook within a few minutes, do not assume failure. Implement a polling fallback that checks the customer's identification status.
// check-verification-status.js
const axios = require('axios');
async function checkVerificationStatus(customerCode) {
var response = await axios.get(
'https://api.paystack.co/customer/' + customerCode,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
var customer = response.data.data;
var identifications = customer.identifications || [];
// Check if any BVN identification exists
var bvnVerification = identifications.find(function(id) {
return id.type === 'bvn';
});
return bvnVerification || null;
}
Name format mismatches. The most common cause of BVN verification failure is name formatting. A user might enter "John" but their bank has "JOHN". Another might type "Chukwuemeka" but the BVN database has "CHUKWUEMEKA JOHN". Normalize names to uppercase and trim whitespace before submitting. This improves match rates significantly.
Storing Results Safely
How you store BVN verification results matters for both compliance and security.
Do store:
- The verification result: "verified" or "failed".
- The timestamp of verification.
- The Paystack customer code linked to the user.
Do not store:
- The raw BVN number itself, unless you have a specific regulatory requirement to do so.
- Any biometric data (you do not receive this, but the principle applies).
// store-verification.js
async function storeVerificationResult(userId, customerCode, verified) {
await db.query(
'INSERT INTO identity_verifications ' +
'(user_id, customer_code, verification_type, verified, verified_at) ' +
'VALUES ($1, $2, $3, $4, NOW()) ' +
'ON CONFLICT (user_id, verification_type) ' +
'DO UPDATE SET verified = $4, verified_at = NOW()',
[userId, customerCode, 'bvn', verified]
);
}
If you must store the BVN: Encrypt it at rest. Use a separate encryption key from your database encryption. Implement access controls so that only the specific service that needs the BVN can decrypt it. Log every access. And set a retention policy: delete it when you no longer need it.
For broader data protection guidance, read the data protection duties guide.
Combining BVN with Account Resolution
BVN verification alone tells you that a person's identity matches bank records. Account resolution tells you that a person owns a specific bank account. Together, they form a strong KYC flow.
A solid sequence:
- User enters their bank name, account number, and BVN.
- You call Resolve Account Number to get the name on the bank account.
- You display the resolved name to the user for confirmation.
- You call Validate Customer with the BVN to verify their identity.
- You compare the resolved account name with the BVN verification name.
- If both pass and the names align, the user is verified.
This two-step approach catches scenarios that either check alone would miss. A fraudster might have a stolen BVN but not the matching bank account. Or they might control a bank account but not have the legitimate owner's BVN.
For a full implementation of this combined flow, see the KYC flow guide.
Testing BVN Verification
BVN verification does not work with test keys. The test environment does not connect to the actual BVN database. You need to build your integration in a way that lets you test the flow without real BVN data.
Mock the webhook. Create a test endpoint that simulates the webhook Paystack would send. Use it to verify your webhook handler processes both success and failure correctly.
// mock-bvn-webhook.js
// For local testing only - send this to your webhook endpoint
var mockSuccess = {
event: 'customeridentification.success',
data: {
customer_code: 'CUS_test123',
email: 'test@example.com',
identification: {
type: 'bvn',
country: 'NG',
},
},
};
var mockFailure = {
event: 'customeridentification.failed',
data: {
customer_code: 'CUS_test123',
email: 'test@example.com',
identification: {
type: 'bvn',
country: 'NG',
},
},
};
When you go live: Test with your own BVN first. Confirm the verification succeeds and the webhook arrives. Then test a deliberate mismatch (enter a wrong name with a valid BVN) to confirm failure handling works. Only then open the feature to users.
For more testing strategies, see the complete testing guide.
Key Takeaways
- ✓BVN verification uses the Validate Customer endpoint, not the Resolve Account endpoint. They serve different purposes.
- ✓You must get prior approval from Paystack before you can use BVN verification. Contact your account manager or submit a request through the dashboard.
- ✓The API does not return the raw BVN data. It tells you whether the information you submitted matches what the bank has on record. This protects the customer.
- ✓BVN verification only works with live keys. Test keys will not reach the BVN database. Build your integration logic with mock responses during development.
- ✓Store the verification result (match or mismatch) and the timestamp, not the BVN itself. Keeping raw BVN numbers in your database creates a compliance and security liability.
- ✓Combine BVN verification with Resolve Account Number for a stronger KYC flow. BVN confirms identity. Account resolution confirms bank ownership.
Frequently Asked Questions
- Can I use BVN verification with Paystack test keys?
- No. BVN verification only works with live keys because it queries the actual BVN database maintained by Nigerian banks. During development, mock the webhook responses to test your integration logic.
- Does Paystack return the actual BVN data to my server?
- No. Paystack returns whether the information you submitted matches what the BVN database has. You do not receive the raw BVN record, biometric data, or any information beyond the match result. This protects the customer.
- How long does BVN verification take?
- The initial API call returns immediately with an acknowledgment. The actual verification result comes via webhook, usually within seconds to a few minutes. Bank system delays can sometimes extend this. Build your UX to handle a short wait.
- What if the BVN verification keeps failing for a legitimate user?
- Name format mismatches are the most common cause. The user may enter "John" but the bank has "JOHN CHUKWUEMEKA". Try normalizing names to uppercase, trimming whitespace, and asking the user for their name exactly as it appears on their bank statement. If it still fails, suggest they contact their bank to confirm their registered details.
- Is BVN verification available for countries outside Nigeria?
- BVN is a Nigerian system. Other countries have their own identity verification systems. Kenya uses national ID numbers, Ghana has the Ghana Card. Paystack may offer different verification types for different countries. Check the current documentation for available options in your target market.
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