Creating Transfer Recipients on Paystack
A Paystack transfer recipient stores the destination account details (bank account number, bank code, or mobile money number) and returns a reusable recipient_code. You create the recipient once using POST /transferrecipient, then reference that code for every transfer. Paystack validates the account at creation time by resolving it with the bank.
What Is a Transfer Recipient
Before Paystack will send money to anyone, you need to tell the system who they are and where the money should go. That is what a transfer recipient does. It packages a person's bank account or mobile money details into a reusable object with a unique recipient_code.
Think of it as saving a contact in your phone before you can call them. You collect their details once, Paystack verifies the details are real, and from that point forward you just reference the code. No re-entering bank numbers. No re-validating.
The recipient object stores the person's name, their account number, the bank code (or mobile money provider), and the currency. Paystack also stores the resolved account name from the bank, which might differ slightly from the name you provided. That resolved name is what the bank recognizes, and it is what you should show your users for confirmation.
Recipients are permanent until you delete them. If a vendor was on your platform two years ago and comes back, their recipient code still works (assuming the bank account is still valid). For the full picture of how recipients fit into the transfer lifecycle, see how Paystack transfers work end to end.
Resolving Accounts Before Creation
Before you create a recipient, you can pre-validate the account using Paystack's Resolve Account endpoint. This checks whether the account number and bank code match a real account and returns the account holder's name.
const resolveAccount = async (accountNumber, bankCode) => {
const url = 'https://api.paystack.co/bank/resolve'
+ '?account_number=' + accountNumber
+ '&bank_code=' + bankCode;
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
if (!data.status) {
// Account does not exist or details are wrong
return null;
}
// data.data.account_name => "AMINA OKAFOR"
// data.data.account_number => "0123456789"
return data.data;
};
// Usage
const resolved = await resolveAccount('0123456789', '058');
if (resolved) {
console.log('Account name: ' + resolved.account_name);
// Show this to the user: "Is this the right person?"
}
Why resolve before creating the recipient? Two reasons. First, it gives you a faster feedback loop. If the user typed the wrong account number, you catch it before you try to create the recipient. Second, it lets you show the resolved name to the user for confirmation. "You are about to set up payouts to AMINA OKAFOR at GTBank. Is this correct?" That confirmation step prevents the most common payout mistake: sending money to the wrong account.
The resolve endpoint is free to call. Use it liberally. Some platforms resolve the account as the user types, triggering the lookup after the user enters all 10 digits of their account number. Others resolve on form submission. Either way, never skip this step.
Creating Nigerian Bank Account Recipients
Nigerian bank accounts use the NUBAN (Nigeria Uniform Bank Account Number) system. Every account has a 10-digit number and a bank code. Paystack uses the nuban type for these recipients.
const createNigerianRecipient = async (name, accountNumber, bankCode) => {
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: 'nuban',
name: name,
account_number: accountNumber,
bank_code: bankCode,
currency: 'NGN',
}),
});
const data = await response.json();
if (!data.status) {
throw new Error('Recipient creation failed: ' + data.message);
}
return {
recipientCode: data.data.recipient_code,
resolvedName: data.data.details.account_name,
bankName: data.data.details.bank_name,
};
};
// Usage
const recipient = await createNigerianRecipient(
'Amina Okafor',
'0123456789',
'058' // GTBank
);
console.log('Recipient code: ' + recipient.recipientCode);
To get bank codes, call the List Banks endpoint:
const listBanks = async (country) => {
const response = await fetch(
'https://api.paystack.co/bank?country=' + country,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const data = await response.json();
// Returns an array of { name, code, ... }
// e.g., { name: "Guaranty Trust Bank", code: "058" }
return data.data;
};
const banks = await listBanks('nigeria');
Cache the bank list in your application. It does not change frequently, and calling it on every page load wastes API calls. Refresh it once a day or once a week.
Kenyan, Ghanaian, and South African Recipients
Each country has its own recipient type and account format.
Kenya: Bank accounts
Kenyan bank recipients use the same nuban type but with Kenyan bank codes and KES currency. Get the bank codes from GET /bank?country=kenya.
const createKenyanBankRecipient = async (name, accountNumber, bankCode) => {
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: 'nuban',
name: name,
account_number: accountNumber,
bank_code: bankCode,
currency: 'KES',
}),
});
return (await response.json()).data;
};
For M-Pesa and Pesalink recipients in Kenya, the setup is different. See transfers to M-Pesa wallets, Paybills, and Tills and Pesalink transfers through Paystack for those specific flows.
Ghana: Bank accounts
Ghanaian bank recipients use the ghipss type (Ghana Interbank Payment and Settlement System).
const createGhanaianBankRecipient = async (name, accountNumber, bankCode) => {
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: 'ghipss',
name: name,
account_number: accountNumber,
bank_code: bankCode,
currency: 'GHS',
}),
});
return (await response.json()).data;
};
Ghana: Mobile money
For mobile money wallets in Ghana (MTN MoMo, Vodafone Cash, AirtelTigo Money), use the mobile_money type. See transfers to Ghanaian mobile money via Paystack for the full setup.
South Africa
South African recipients use the basa type (Banking Association South Africa) with ZAR currency and South African bank codes.
Listing, Updating, and Deleting Recipients
Over time, your system accumulates recipients. Paystack provides endpoints to manage them.
Listing recipients
const listRecipients = async (page, perPage) => {
const url = 'https://api.paystack.co/transferrecipient'
+ '?perPage=' + (perPage || 50)
+ '&page=' + (page || 1);
const response = await fetch(url, {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
return data.data; // Array of recipient objects
};
In practice, you rarely list recipients from Paystack because you store recipient codes in your own database tied to vendor or user records. The list endpoint is useful for reconciliation: comparing what Paystack has against what your database says.
Updating a recipient
When a vendor changes banks, update the recipient rather than creating a new one:
const updateRecipient = async (recipientIdOrCode, newName, newEmail) => {
const response = await fetch(
'https://api.paystack.co/transferrecipient/' + recipientIdOrCode,
{
method: 'PUT',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: newName,
email: newEmail,
}),
}
);
return (await response.json()).data;
};
Note that you can update metadata fields like name and email, but changing the actual bank account details may require creating a new recipient. Check the Paystack documentation for current behavior, as this has evolved over time. If you need to change bank details, the safest pattern is: create a new recipient with the new bank details, update your database to point to the new recipient code, and optionally delete the old recipient.
Deleting a recipient
const deleteRecipient = async (recipientIdOrCode) => {
const response = await fetch(
'https://api.paystack.co/transferrecipient/' + recipientIdOrCode,
{
method: 'DELETE',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
return (await response.json()).status; // true if deleted
};
Only delete recipients you are certain you will never need again. If a vendor leaves your platform, you might keep the recipient for historical reference and reconciliation purposes. Soft-delete in your database rather than hard-deleting on Paystack.
Storing Recipients in Your Database
Your database should be the primary source of truth for recipient data. Paystack's recipient list is a backup. Here is a practical schema.
// Example database schema for a vendors table
// vendor_id (primary key)
// user_id (foreign key to users)
// business_name (string)
// bank_code (string, e.g., "058")
// bank_name (string, e.g., "Guaranty Trust Bank")
// account_number (string, e.g., "0123456789")
// resolved_account_name (string, from Paystack resolution)
// paystack_recipient_code (string, e.g., "RCP_abc123")
// recipient_active (boolean)
// created_at (timestamp)
// updated_at (timestamp)
// When onboarding a vendor
async function onboardVendor(userId, bankDetails) {
// Step 1: Resolve the account
const resolved = await resolveAccount(
bankDetails.accountNumber,
bankDetails.bankCode
);
if (!resolved) {
throw new Error('Could not verify bank account');
}
// Step 2: Create recipient on Paystack
const recipient = await createNigerianRecipient(
resolved.account_name,
bankDetails.accountNumber,
bankDetails.bankCode
);
// Step 3: Store in your database
await db.vendors.create({
userId: userId,
businessName: bankDetails.businessName,
bankCode: bankDetails.bankCode,
bankName: recipient.bankName,
accountNumber: bankDetails.accountNumber,
resolvedAccountName: resolved.account_name,
paystackRecipientCode: recipient.recipientCode,
recipientActive: true,
});
}
Key patterns to follow:
- Store both the bank details and the recipient code. If you ever need to recreate the recipient (migration, new Paystack account), you have the raw details.
- Store the resolved account name from Paystack. This is the name the bank recognizes and what you should display in your admin tools.
- Use a boolean flag for active/inactive rather than deleting vendor records. Inactive vendors can be reactivated later.
- Add a unique constraint on
paystack_recipient_codeto prevent duplicate entries.
Validation and Error Handling
Recipient creation can fail for several reasons. Handle each one explicitly.
Invalid account number
The account number does not exist at the specified bank. The Resolve Account endpoint catches this before you even try to create the recipient. If you skip resolution and go straight to creation, Paystack will reject it with a validation error.
Invalid bank code
The bank code you provided does not exist in Paystack's bank list. This usually means a frontend bug where the bank code got corrupted, or you are using a bank code from one country with a different country's currency. Always source bank codes from the List Banks endpoint for the correct country.
Duplicate recipient
If you try to create a recipient with the same account number and bank code as an existing recipient, Paystack may return the existing recipient instead of creating a new one. Your code should handle this gracefully rather than treating it as an error.
async function getOrCreateRecipient(name, accountNumber, bankCode, 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: currency === 'NGN' ? 'nuban' : 'ghipss',
name: name,
account_number: accountNumber,
bank_code: bankCode,
currency: currency,
}),
});
const data = await response.json();
if (!data.status) {
// Log the full error for debugging
console.error('Recipient creation failed:', data.message);
throw new Error('Could not create recipient: ' + data.message);
}
// Works whether Paystack created a new one or returned an existing one
return data.data.recipient_code;
}
Network errors
Recipient creation is a safe operation to retry. If the request times out or your server loses connectivity before receiving the response, try again. Even if the first request succeeded silently, Paystack handles the duplicate gracefully. This is one of the few Paystack operations where retry is always safe.
Bulk Recipient Creation
When onboarding many vendors at once (migrating from another platform, importing a vendor list), creating recipients one by one is slow. Paystack provides a bulk endpoint.
const createBulkRecipients = async (recipients) => {
const response = await fetch('https://api.paystack.co/transferrecipient/bulk', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
batch: recipients.map(r => ({
type: 'nuban',
name: r.name,
account_number: r.accountNumber,
bank_code: r.bankCode,
currency: 'NGN',
})),
}),
});
const data = await response.json();
return data.data; // Array of created recipients
};
// Usage
const vendors = [
{ name: 'Amina Okafor', accountNumber: '0123456789', bankCode: '058' },
{ name: 'Chidi Nwosu', accountNumber: '9876543210', bankCode: '044' },
{ name: 'Fatima Bello', accountNumber: '5432167890', bankCode: '011' },
];
const created = await createBulkRecipients(vendors);
// Match each created recipient back to your vendor record
Some entries in the batch may fail while others succeed. Process the response carefully: map each returned recipient back to your vendor record using the account number, and flag any that failed for manual review. The bulk endpoint saves significant time when you have hundreds of vendors to onboard, but it does not skip validation. Each entry still gets resolved against the bank.
For the complete guide on building payout systems that depend on well-managed recipients, see building an automated payout system on Paystack.
Key Takeaways
- ✓Transfer recipients store destination bank or mobile money details and return a reusable recipient_code. Create the recipient once, then reference it for all future transfers to that person.
- ✓Paystack validates account details at creation by resolving the account with the bank. If the account number and bank code do not match, creation fails immediately.
- ✓Recipient types vary by country: nuban for Nigeria, ghipss for Ghanaian banks, mobile_money for Ghanaian mobile wallets, and basa for South Africa. Kenya uses nuban with Kenyan bank codes.
- ✓Always show the resolved account_name to your user for confirmation before proceeding. This is your strongest defense against sending money to the wrong person.
- ✓Store recipient codes in your database tied to the user or vendor record. Do not recreate recipients every time you want to send money.
- ✓You can update a recipient if the person changes banks. Use PUT /transferrecipient/{id_or_code} with the new bank details.
- ✓Use the Resolve Account endpoint (GET /bank/resolve) to pre-validate account details before attempting recipient creation. This gives you faster feedback and a better user experience.
Frequently Asked Questions
- Can I create a recipient without the person having a Paystack account?
- Yes. Transfer recipients are just bank account or mobile money details. The person receiving the money does not need a Paystack account. They receive the transfer directly into their bank account or mobile wallet, just like any other bank transfer.
- What happens if I create two recipients with the same bank details?
- Paystack may return the existing recipient rather than creating a duplicate. Your code should handle this gracefully. In your database, use a unique constraint on the account number and bank code combination to prevent duplicate vendor records on your side.
- How do I handle a vendor who changes their bank account?
- Create a new recipient with the updated bank details, update your database to point to the new recipient code, and optionally delete or deactivate the old recipient. Resolve the new account details with the vendor first to confirm the account name matches.
- Does Paystack validate the account name against what I provide?
- Paystack resolves the account name from the bank, but the name you provide in the creation request does not need to match exactly. The resolved name from the bank is stored in the recipient details. Always use the resolved name for display and confirmation purposes.
- Are there limits on how many recipients I can create?
- Paystack does not publicly state a hard limit on the number of recipients per business. Large platforms with thousands of recipients operate normally. If you plan to create recipients in very large volumes, consider using the bulk endpoint and check with Paystack support about any rate limits.
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