Paystack Recipient Creation Failed
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.
What the Error Looks Like
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.
Every Cause of Recipient Creation Failure
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 GhIPSS
If 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.
Validate with the Resolve Account API First
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.
Complete Validated Recipient Creation Flow
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.
Getting Valid Bank Codes
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.
Handling Account Name Mismatches
The Resolve Account API returns the name registered on the bank account. This might not match the name your customer provided. Common reasons:
- The customer typed "John Doe" but the bank has "JOHN ADEBAYO DOE."
- The customer used a nickname but the bank has their legal name.
- The account belongs to a business, and the customer gave a personal name.
- The customer entered someone else's account number by mistake.
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.
Mobile Money Recipients
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:
- The
account_numberis the phone number, not a bank account number. - The
bank_codeis the mobile money provider code (get this from the List Banks API with the appropriate country filter). - The
typemust be"mobile_money". - Phone number format matters. Use the format Paystack expects for the country (usually with country code, like
"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.
Troubleshooting Checklist
When recipient creation fails, work through this checklist:
- Is the bank code valid? Call
GET /bank?country=your_countryand check if your bank code appears in the response. If not, the code is wrong. - Is the account number in the right format? Nigerian NUBAN: 10 digits. Check the format for other countries.
- Does the account exist? Call
GET /bank/resolve?account_number=...&bank_code=.... If this fails, the account does not exist or the bank cannot verify it. - Is the recipient type correct? NGN uses
"nuban", GHS uses"ghipss", ZAR uses"basa", KES uses"mobile_money". - Is the currency correct? The currency must match the country of the bank. You cannot create an NGN recipient with a Ghanaian bank code.
- Is the bank system up? If the resolve call returns a timeout or server error, the bank's verification system might be down. Wait 15 to 30 minutes and try again.
- Are you using live keys for live accounts? Test keys only work with test data. You cannot resolve real bank accounts with test keys.
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.
Further Reading
For more on Paystack transfers and troubleshooting:
- Paystack Errors and Troubleshooting: The Complete Reference covers all common Paystack errors.
- Paystack Transfer Failed: Reasons and Recovery explains what happens after recipient creation succeeds but the transfer itself fails.
- Paystack Transfer OTP Required Error covers the OTP requirement for automated transfers.
- Paystack Insufficient Balance on Transfer covers balance issues that block transfers.
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.
Key Takeaways
- ✓Always validate bank account details with the Resolve Account API (GET /bank/resolve) before creating a transfer recipient. This catches invalid details before you hit the error.
- ✓Invalid bank code is the most common cause. Use the Paystack List Banks API (GET /bank) to get the correct bank codes for each country. Do not hardcode bank codes because they can change.
- ✓Account numbers must be exactly 10 digits for Nigerian banks (NUBAN format). Kenyan, Ghanaian, and South African account numbers have different formats. Validate the length and format before calling the API.
- ✓The recipient type must match the transfer method: "nuban" for Nigerian bank accounts, "mobile_money" for mobile money, "basa" for South African bank accounts, "ghipss" for Ghanaian banks. Using the wrong type causes rejection.
- ✓Account name mismatch is not a creation failure but can cause transfer failures later. Compare the name returned by the Resolve API with the name your user provided. Flag discrepancies before proceeding.
- ✓Cache the bank list from the List Banks API. It rarely changes, and calling it for every recipient creation adds unnecessary latency.
Frequently Asked Questions
- Can I create a recipient without resolving the account first?
- Technically yes. The Resolve Account API is a separate call. But skipping it means you will discover invalid details when the recipient creation fails, and the error message from Paystack is less informative than the Resolve API response. Always resolve first.
- Does the recipient name have to match the bank account name exactly?
- No. The name field on the recipient is for your reference. Paystack does not reject a recipient because the name you provided differs from the bank account name. However, some banks may flag transfers where the name does not match. Use the name returned by the Resolve Account API for best results.
- Can I reuse a recipient code for multiple transfers?
- Yes. Once a recipient is created, you receive a recipient_code. Use this code for all future transfers to that bank account. You do not need to create a new recipient every time. Store the recipient_code in your database alongside the user or vendor record.
- What happens if a bank changes its bank code?
- Existing recipients created with the old code continue to work. The recipient_code is the identifier Paystack uses internally, not the bank code. For new recipients, use the updated code from the List Banks API. This is why you should not hardcode bank codes.
- Why does recipient creation work in test mode but fail in live mode?
- Test mode uses simulated bank verification. Any reasonable-looking account number will pass. Live mode verifies against real banks. If the account does not exist or the bank code is wrong, live mode fails. Always test with real account details when switching to live mode.
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