The Verification API helps you validate bank account numbers and card BINs before processing transactions. This reduces failed transactions and improves user experience.
Overview
Verification services include:
- Bank account name resolution
- Account number validation
- Card BIN lookup
- Bank information retrieval
Some verification endpoints may incur charges. Check Paystack’s pricing page for details.
Resolve Bank Account
Retrieve the account name for a bank account number.
Initialize the SDK
import { Paystack } from '@efobi/paystack';
const paystack = new Paystack(process.env.PAYSTACK_SECRET_KEY!);
Resolve the account
const { data, error } = await paystack.verification.resolveAccount({
account_number: '0123456789',
bank_code: '058' // GTBank code
});
if (error) {
console.error('Resolution failed:', error);
return;
}
if (data) {
console.log('Account Name:', data.data.account_name);
console.log('Account Number:', data.data.account_number);
// Display to user for confirmation
const confirmed = await confirmWithUser(
`Is this correct? ${data.data.account_name}`
);
}
Parameters
Bank account number to resolve
Use account resolution to verify recipient details before creating transfer recipients or initiating transfers.
Validate Bank Account
Validate a customer’s account using various verification methods.
const { data, error } = await paystack.verification.validateAccount({
account_name: 'John Doe',
account_number: '0123456789',
account_type: 'personal', // or 'business'
bank_code: '058',
country_code: 'NG',
document_type: 'identityNumber',
document_number: '1234567890'
});
if (data) {
console.log('Verified:', data.data.verified);
console.log('Message:', data.data.verificationMessage);
if (data.data.verified) {
console.log('Account validation successful!');
// Proceed with transaction
} else {
console.log('Validation failed:', data.data.verificationMessage);
// Show error to user
}
}
Validation Parameters
Account type: personal or business
Two-letter country code (e.g., NG, GH, ZA)
Type of identity document: identityNumber, passportNumber, businessRegistrationNumber
Account validation may incur charges per request. Use it only when necessary.
Resolve Card BIN
Get information about a card from its BIN (first 6 digits).
const { data, error } = await paystack.verification.resolveCardBin({
card_bin: '539983' // First 6 digits of card
});
if (data) {
console.log('Card details:', {
bin: data.data.bin,
brand: data.data.brand, // visa, mastercard, verve
subBrand: data.data.sub_brand,
cardType: data.data.card_type, // debit or credit
bank: data.data.bank,
countryCode: data.data.country_code,
countryName: data.data.country_name
});
// Show card info to user
displayCardInfo(data.data);
}
Response Fields
Card BIN (first 6 digits)
Card brand: visa, mastercard, verve, etc.
Card sub-brand (e.g., ELECTRON, CLASSIC)
Card type: debit or credit
Linked bank ID in Paystack system
Error Handling
Handle verification errors appropriately:
try {
const { data, error } = await paystack.verification.resolveAccount({
account_number: accountNumber,
bank_code: bankCode
});
if (error) {
console.error('Verification error:', error);
return {
success: false,
message: 'Unable to verify account details'
};
}
if (!data.status) {
// API returned error
return {
success: false,
message: data.message || 'Account not found'
};
}
return {
success: true,
accountName: data.data.account_name,
accountNumber: data.data.account_number
};
} catch (err) {
console.error('Unexpected error:', err);
return {
success: false,
message: 'Verification service unavailable'
};
}
Best Practices
- Cache resolution results to avoid repeated API calls
- Show account names to users for confirmation
- Handle network errors gracefully
- Validate input before making API calls
- Use BIN lookup to provide better UX during card entry
- Implement retries for transient failures
- Log verification attempts for debugging
- Consider rate limits when doing bulk verifications
Common Scenarios
Transfer Recipient Creation
async function createVerifiedRecipient(
accountNumber: string,
bankCode: string
) {
// Step 1: Resolve account name
const { data, error } = await paystack.verification.resolveAccount({
account_number: accountNumber,
bank_code: bankCode
});
if (error || !data) {
throw new Error('Could not verify account');
}
const accountName = data.data.account_name;
// Step 2: Confirm with user
const confirmed = await confirmAccountDetails({
accountName,
accountNumber,
bankCode
});
if (!confirmed) {
throw new Error('User rejected account details');
}
// Step 3: Create transfer recipient
const recipient = await paystack.transferRecipient.create({
type: 'nuban',
name: accountName,
account_number: accountNumber,
bank_code: bankCode,
currency: 'NGN'
});
return recipient.data?.data?.recipient_code;
}
Card Payment Validation
async function validateCardBeforePayment(cardNumber: string) {
const bin = cardNumber.substring(0, 6);
const { data, error } = await paystack.verification.resolveCardBin({
card_bin: bin
});
if (error || !data) {
return {
valid: false,
message: 'Could not validate card'
};
}
const cardInfo = data.data;
// Check if card is supported
if (!['visa', 'mastercard', 'verve'].includes(cardInfo.brand.toLowerCase())) {
return {
valid: false,
message: `${cardInfo.brand} cards are not supported`
};
}
// Show card details to user
return {
valid: true,
cardInfo: {
brand: cardInfo.brand,
type: cardInfo.card_type,
bank: cardInfo.bank,
country: cardInfo.country_name
}
};
}
Batch Account Verification
async function verifyMultipleAccounts(
accounts: Array<{ accountNumber: string; bankCode: string }>
) {
const results = [];
for (const account of accounts) {
try {
const { data, error } = await paystack.verification.resolveAccount({
account_number: account.accountNumber,
bank_code: account.bankCode
});
if (data && data.status) {
results.push({
accountNumber: account.accountNumber,
bankCode: account.bankCode,
accountName: data.data.account_name,
verified: true
});
} else {
results.push({
accountNumber: account.accountNumber,
bankCode: account.bankCode,
verified: false,
error: data?.message || 'Verification failed'
});
}
} catch (err) {
results.push({
accountNumber: account.accountNumber,
bankCode: account.bankCode,
verified: false,
error: 'Network error'
});
}
// Rate limiting: wait 100ms between requests
await new Promise(resolve => setTimeout(resolve, 100));
}
return results;
}
import { useState, useEffect } from 'react';
function BankAccountForm() {
const [accountNumber, setAccountNumber] = useState('');
const [bankCode, setBankCode] = useState('');
const [accountName, setAccountName] = useState('');
const [verifying, setVerifying] = useState(false);
const [verified, setVerified] = useState(false);
useEffect(() => {
// Auto-verify when both fields are filled
if (accountNumber.length === 10 && bankCode) {
verifyAccount();
}
}, [accountNumber, bankCode]);
const verifyAccount = async () => {
setVerifying(true);
setVerified(false);
try {
const response = await fetch('/api/verify-account', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accountNumber, bankCode })
});
const result = await response.json();
if (result.success) {
setAccountName(result.accountName);
setVerified(true);
} else {
setAccountName('');
alert('Could not verify account');
}
} catch (error) {
console.error('Verification failed:', error);
} finally {
setVerifying(false);
}
};
return (
<form>
<input
value={accountNumber}
onChange={(e) => setAccountNumber(e.target.value)}
placeholder="Account Number"
maxLength={10}
/>
<select value={bankCode} onChange={(e) => setBankCode(e.target.value)}>
<option value="">Select Bank</option>
<option value="058">GTBank</option>
<option value="033">UBA</option>
{/* More banks */}
</select>
{verifying && <p>Verifying...</p>}
{verified && accountName && (
<div className="success">
<p>✓ Account Name: {accountName}</p>
</div>
)}
</form>
);
}
Caching Strategies
Cache verification results to reduce API calls:
import { LRUCache } from 'lru-cache';
// Create cache with 1000 entries, 1 hour TTL
const verificationCache = new LRUCache<string, any>({
max: 1000,
ttl: 1000 * 60 * 60 // 1 hour
});
async function resolveAccountCached(
accountNumber: string,
bankCode: string
) {
const cacheKey = `${bankCode}:${accountNumber}`;
// Check cache first
const cached = verificationCache.get(cacheKey);
if (cached) {
console.log('Using cached result');
return cached;
}
// Verify with API
const { data, error } = await paystack.verification.resolveAccount({
account_number: accountNumber,
bank_code: bankCode
});
if (data && data.status) {
// Cache successful result
verificationCache.set(cacheKey, data.data);
return data.data;
}
throw new Error(error?.message || 'Verification failed');
}
Rate Limiting
Implement rate limiting for bulk verifications:
import pLimit from 'p-limit';
// Limit to 5 concurrent requests
const limit = pLimit(5);
async function verifyAccountsBulk(
accounts: Array<{ accountNumber: string; bankCode: string }>
) {
const promises = accounts.map(account =>
limit(async () => {
const { data } = await paystack.verification.resolveAccount({
account_number: account.accountNumber,
bank_code: account.bankCode
});
return {
...account,
accountName: data?.data?.account_name,
verified: !!data?.status
};
})
);
return Promise.all(promises);
}