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.
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:
For the full picture of how BVN fits alongside other identity APIs, see the complete identity verification guide.
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:
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.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
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.
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.
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.
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.
How you store BVN verification results matters for both compliance and security.
Do store:
Do not store:
// 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.
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:
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.
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.
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