Skip to main content
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.
1

Initialize the SDK

import { Paystack } from '@efobi/paystack';

const paystack = new Paystack(process.env.PAYSTACK_SECRET_KEY!);
2

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

account_number
string
required
Bank account number to resolve
bank_code
string
required
Bank code (see Miscellaneous API for bank codes)
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_name
string
required
Name on the bank account
account_number
string
required
Bank account number
account_type
string
required
Account type: personal or business
bank_code
string
required
Bank code
country_code
string
required
Two-letter country code (e.g., NG, GH, ZA)
document_type
string
required
Type of identity document: identityNumber, passportNumber, businessRegistrationNumber
document_number
string
required
Identity document number
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

bin
string
Card BIN (first 6 digits)
brand
string
Card brand: visa, mastercard, verve, etc.
sub_brand
string
Card sub-brand (e.g., ELECTRON, CLASSIC)
card_type
string
Card type: debit or credit
bank
string
Issuing bank name
country_code
string
Two-letter country code
country_name
string
Full country name
linked_bank_id
number
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

  1. Cache resolution results to avoid repeated API calls
  2. Show account names to users for confirmation
  3. Handle network errors gracefully
  4. Validate input before making API calls
  5. Use BIN lookup to provide better UX during card entry
  6. Implement retries for transient failures
  7. Log verification attempts for debugging
  8. 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;
}

Form Validation with Real-time Verification

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);
}