Recipient creation fails when Paystack cannot verify the bank account details you provided. The most common causes are an invalid bank code, a wrong account number, an account number that does not belong to the specified bank, or an incorrect recipient type. Before creating any recipient, call GET /bank/resolve with the account number and bank code. If the resolve call succeeds, use the returned account name and details to create the recipient. If it fails, the account details are wrong.
When you call POST /transferrecipient with invalid details, Paystack returns errors like these:
{
"status": false,
"message": "Could not resolve account name. Check parameters or try again."
}
Or:
{
"status": false,
"message": "Invalid Bank Code"
}
Or:
{
"status": false,
"message": "Account number is not valid for this bank"
}
The HTTP status code is 400 in all cases. The recipient is not created. You cannot proceed with a transfer until you fix the input and successfully create the recipient.
Here are the specific reasons recipient creation fails, from most common to least common:
1. Invalid bank code. You passed a bank code that Paystack does not recognize. Bank codes are country-specific. "058" might be a valid code for GTBank in Nigeria but means nothing for a Kenyan bank. Always use the List Banks API to get current codes for the correct country.
2. Wrong account number format. Nigerian bank accounts use the NUBAN format: exactly 10 digits. If you pass 9 digits, 11 digits, or include letters or spaces, the call fails. Other countries have different formats. Kenyan bank accounts vary by bank. Ghanaian accounts follow their own format.
3. Account number does not exist at that bank. The format is correct, the bank code is correct, but no account with that number exists at that bank. The customer may have given you the wrong number or the wrong bank.
4. Wrong recipient type. The type field must match the transfer method:
"nuban" for Nigerian bank accounts"mobile_money" for mobile money recipients (Ghana, Kenya)"basa" for South African bank accounts"ghipss" for Ghanaian bank accounts via GhIPSSIf you pass "nuban" for a Ghanaian bank account, or "mobile_money" for a Nigerian bank account, the call fails.
5. Bank is temporarily unavailable. Paystack verifies the account details with the bank in real time. If the bank's system is down for maintenance or experiencing issues, the verification fails. This is not your fault, but you still get an error. Retry after a few minutes.
6. Account is dormant or closed. Some banks reject verification requests for accounts that have been dormant for a long period or that have been closed. The customer may not know their account has been flagged.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
The Resolve Account API lets you verify that an account number and bank code combination is valid before you try to create a recipient. It also returns the account holder's name, which you can use to confirm the details with the customer.
async function resolveAccount(accountNumber, bankCode) {
const params = new URLSearchParams({
account_number: accountNumber,
bank_code: bankCode,
});
const response = await fetch(
`https://api.paystack.co/bank/resolve?${params}`,
{
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
},
}
);
const data = await response.json();
if (data.status) {
return {
valid: true,
accountNumber: data.data.account_number,
accountName: data.data.account_name,
bankId: data.data.bank_id,
};
}
return {
valid: false,
message: data.message,
};
}
// Usage
const result = await resolveAccount('0123456789', '058');
// Success: { valid: true, accountNumber: '0123456789', accountName: 'JOHN DOE', bankId: 9 }
// Failure: { valid: false, message: 'Could not resolve account name...' }
If the resolve call succeeds, the account exists and is active. Use the returned details to create the recipient. If it fails, do not attempt to create the recipient. Show the customer an error and ask them to double-check their details.
Here is a production-ready flow that validates, confirms, and creates the recipient:
async function createValidatedRecipient(accountNumber, bankCode, currency = 'NGN') {
// Step 1: Clean the input
const cleanAccountNumber = accountNumber.replace(/\s/g, '').trim();
// Step 2: Basic format validation
if (currency === 'NGN' && cleanAccountNumber.length !== 10) {
return {
success: false,
reason: 'invalid_format',
message: 'Nigerian account numbers must be exactly 10 digits.',
};
}
if (!/^\d+$/.test(cleanAccountNumber)) {
return {
success: false,
reason: 'invalid_format',
message: 'Account number must contain only digits.',
};
}
// Step 3: Resolve the account
const resolved = await resolveAccount(cleanAccountNumber, bankCode);
if (!resolved.valid) {
return {
success: false,
reason: 'resolve_failed',
message: resolved.message,
};
}
// Step 4: Determine recipient type based on currency
const recipientType = getRecipientType(currency);
// Step 5: Create the recipient
const response = await fetch('https://api.paystack.co/transferrecipient', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: recipientType,
name: resolved.accountName,
account_number: cleanAccountNumber,
bank_code: bankCode,
currency,
}),
});
const data = await response.json();
if (data.status) {
return {
success: true,
recipientCode: data.data.recipient_code,
accountName: resolved.accountName,
details: data.data,
};
}
return {
success: false,
reason: 'creation_failed',
message: data.message,
};
}
function getRecipientType(currency) {
const types = {
NGN: 'nuban',
GHS: 'ghipss',
ZAR: 'basa',
KES: 'mobile_money',
};
return types[currency] || 'nuban';
}
This flow catches most errors before they reach Paystack. Format validation catches typos. The Resolve API catches invalid accounts. By the time you call POST /transferrecipient, you know the account is valid.
Never hardcode bank codes. Use the List Banks API to get current codes, and let your users select from the list.
async function listBanks(country = 'nigeria') {
const response = await fetch(
`https://api.paystack.co/bank?country=${country}&perPage=100`,
{
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
},
}
);
const data = await response.json();
if (data.status) {
return data.data.map(bank => ({
name: bank.name,
code: bank.code,
type: bank.type,
currency: bank.currency,
}));
}
return [];
}
// Cache the bank list. It changes rarely.
let cachedBanks = null;
let cacheTimestamp = 0;
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
async function getBanks(country = 'nigeria') {
const now = Date.now();
if (cachedBanks && (now - cacheTimestamp) < CACHE_TTL) {
return cachedBanks;
}
cachedBanks = await listBanks(country);
cacheTimestamp = now;
return cachedBanks;
}
Build a bank selector dropdown in your UI that pulls from this cached list. When the user selects a bank, you have the correct bank code. This eliminates the most common cause of recipient creation failure.
Valid country values include "nigeria", "ghana", "south-africa", and "kenya". Use the country that matches the currency of the transfer.
The Resolve Account API returns the name registered on the bank account. This might not match the name your customer provided. Common reasons:
Name mismatches do not prevent recipient creation, but they can indicate a problem. If a customer says "pay me" and gives you an account in a completely different name, that is worth flagging.
function compareNames(providedName, resolvedName) {
const normalize = (name) =>
name.toUpperCase().replace(/[^A-Z\s]/g, '').split(/\s+/).sort().join(' ');
const normalizedProvided = normalize(providedName);
const normalizedResolved = normalize(resolvedName);
if (normalizedProvided === normalizedResolved) {
return { match: true, confidence: 'exact' };
}
// Check if all words in the provided name appear in the resolved name
const providedWords = normalizedProvided.split(' ');
const resolvedWords = normalizedResolved.split(' ');
const allProvidedInResolved = providedWords.every(w => resolvedWords.includes(w));
if (allProvidedInResolved) {
return { match: true, confidence: 'partial' };
}
// Check if at least the surname matches
const surnameMatch = providedWords.some(w => resolvedWords.includes(w));
if (surnameMatch) {
return { match: false, confidence: 'low', warning: 'Partial name match only.' };
}
return {
match: false,
confidence: 'none',
warning: `Account name "${resolvedName}" does not match provided name "${providedName}". Please confirm.`,
};
}
Use this to display the resolved name to the customer for confirmation. "The account name on record is JOHN ADEBAYO DOE. Is this correct?" This prevents payments to the wrong account.
Creating mobile money recipients (for Ghana and Kenya) has different requirements than bank account recipients:
async function createMobileMoneyRecipient(name, phone, provider, currency) {
const response = await fetch('https://api.paystack.co/transferrecipient', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'mobile_money',
name,
account_number: phone, // Phone number as account number
bank_code: provider, // Mobile money provider code
currency,
}),
});
const data = await response.json();
if (data.status) {
return { success: true, recipientCode: data.data.recipient_code };
}
return { success: false, message: data.message };
}
Key differences for mobile money:
account_number is the phone number, not a bank account number.bank_code is the mobile money provider code (get this from the List Banks API with the appropriate country filter).type must be "mobile_money"."233244123456" for Ghana).Common failures with mobile money recipients include wrong provider code, phone number in the wrong format (with or without country code), and the phone number not being registered for mobile money on that provider.
When recipient creation fails, work through this checklist:
GET /bank?country=your_country and check if your bank code appears in the response. If not, the code is wrong.GET /bank/resolve?account_number=...&bank_code=.... If this fails, the account does not exist or the bank cannot verify it."nuban", GHS uses "ghipss", ZAR uses "basa", KES uses "mobile_money".If all of these check out and creation still fails, contact Paystack support with the full request body and error response. There may be a restriction on the specific account or bank.
For more on Paystack transfers and troubleshooting:
Building robust transfer systems means handling every failure mode. The McTaba Full-Stack Software and AI Engineering course teaches you to build payment integrations that handle real-world edge cases from day one.
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