Call GET /balance to check your available Paystack balance before initiating transfers. The response returns an array of balances per currency. Compare the available balance against your transfer amount plus estimated fees. For batch payouts, sum all transfers and check before sending the batch to avoid a full-batch rejection.
Paystack exposes your balance through a simple GET endpoint. The response contains an array of balance objects, one per currency your account operates in.
const getBalance = async () => {
const response = await fetch('https://api.paystack.co/balance', {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
return data.data;
};
// Example response
// [
// { currency: "NGN", balance: 5000000 },
// { currency: "KES", balance: 250000 }
// ]
// Amounts are in the smallest unit (kobo, cents)
The balance value is in the smallest currency unit. So 5000000 in NGN is 50,000 Naira, and 250000 in KES is 2,500 Kenyan Shillings. Apply the same conversion you use for transfer amounts.
This endpoint is lightweight and fast. Call it freely before transfers. There is no cost to checking your balance, and skipping the check costs you a failed transfer and a support ticket.
This is the distinction that trips up most developers when they first work with Paystack transfers. Your total balance includes all money in your Paystack account, but your available balance only includes the portion that has completed settlement and is ready to transfer.
When a customer pays you, the money enters your Paystack account, but it is not immediately available. Paystack settles funds to your available balance on a schedule that depends on your account configuration and market. Until settlement completes, those funds are "pending" and cannot be used for transfers.
If your dashboard shows a total balance of 500,000 NGN but your available balance is only 200,000 NGN, you can only transfer up to 200,000 NGN (minus fees). The remaining 300,000 NGN is still being settled from recent customer payments.
async function getAvailableBalance(currency) {
var balances = await getBalance();
for (var i = 0; i < balances.length; i++) {
if (balances[i].currency === currency) {
return {
total: balances[i].balance,
currency: currency,
// Convert for display
displayAmount: balances[i].balance / 100,
};
}
}
return { total: 0, currency: currency, displayAmount: 0 };
}
Plan your payout schedules around settlement timing. If your settlement cycle is T+1 (next business day), payments received on Friday might not be available until Monday. Schedule payouts for Tuesday to ensure Friday's payments have settled.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
async function canAffordTransfer(amountMinorUnit, currency) {
var balance = await getAvailableBalance(currency);
// Estimate the fee (this is a rough estimate)
// Check Paystack pricing for actual fee structure
var estimatedFee = Math.max(1000, Math.round(amountMinorUnit * 0.01));
var totalNeeded = amountMinorUnit + estimatedFee;
return {
canAfford: balance.total >= totalNeeded,
available: balance.total,
transferAmount: amountMinorUnit,
estimatedFee: estimatedFee,
totalNeeded: totalNeeded,
shortfall: Math.max(0, totalNeeded - balance.total),
};
}
// Usage
var check = await canAffordTransfer(500000, 'NGN'); // 5,000 NGN
if (!check.canAfford) {
console.error(
'Insufficient balance. Need: ' + (check.totalNeeded / 100)
+ ' NGN, Available: ' + (check.available / 100) + ' NGN'
);
// Alert finance team or queue for later
}
The fee estimate in the code above is a placeholder. Paystack's actual transfer fees depend on the amount, destination type, and your specific account terms. Check the Paystack pricing page or your dashboard for the current fee structure, and update your estimation logic accordingly.
Batch payouts are where balance checks matter most. If the batch total exceeds your balance, Paystack rejects the entire batch. You do not get a partial execution where some transfers go through.
async function validateBatchPayout(payouts, currency) {
// Sum all payout amounts
var totalAmount = 0;
for (var i = 0; i < payouts.length; i++) {
totalAmount += payouts[i].amountMinorUnit;
}
// Estimate fees per transfer
var totalEstimatedFees = 0;
for (var j = 0; j < payouts.length; j++) {
totalEstimatedFees += Math.max(1000, Math.round(payouts[j].amountMinorUnit * 0.01));
}
var totalNeeded = totalAmount + totalEstimatedFees;
var balance = await getAvailableBalance(currency);
var result = {
payoutCount: payouts.length,
totalPayoutAmount: totalAmount,
estimatedFees: totalEstimatedFees,
totalNeeded: totalNeeded,
available: balance.total,
canAfford: balance.total >= totalNeeded,
shortfall: Math.max(0, totalNeeded - balance.total),
};
if (!result.canAfford) {
// Calculate how many payouts we can afford
var sorted = payouts.slice().sort(function(a, b) {
return a.amountMinorUnit - b.amountMinorUnit;
});
var runningTotal = 0;
var affordableCount = 0;
for (var k = 0; k < sorted.length; k++) {
var withFee = sorted[k].amountMinorUnit
+ Math.max(1000, Math.round(sorted[k].amountMinorUnit * 0.01));
if (runningTotal + withFee <= balance.total) {
runningTotal += withFee;
affordableCount++;
} else {
break;
}
}
result.affordablePayouts = affordableCount;
result.suggestion = 'Can afford ' + affordableCount + ' of '
+ payouts.length + ' payouts. Fund balance with at least '
+ (result.shortfall / 100) + ' ' + currency + ' to cover all.';
}
return result;
}
This function not only tells you whether you can afford the batch, but also suggests how many payouts you could process with the current balance if you prioritize by amount (smallest first). This is useful when your finance team needs to decide: fund the balance and wait, or process a partial batch now.
// Run this as a cron job, e.g., every hour or 4 hours before scheduled payouts
async function checkBalanceAlerts() {
var currencies = ['NGN', 'KES', 'GHS'];
var thresholds = {
NGN: 10000000, // Alert if below 100,000 NGN
KES: 500000, // Alert if below 5,000 KES
GHS: 200000, // Alert if below 2,000 GHS
};
for (var i = 0; i < currencies.length; i++) {
var currency = currencies[i];
var balance = await getAvailableBalance(currency);
var threshold = thresholds[currency] || 0;
if (balance.total < threshold) {
await sendAlert({
type: 'low_balance',
currency: currency,
currentBalance: balance.total / 100,
threshold: threshold / 100,
message: currency + ' balance is ' + (balance.total / 100)
+ '. Threshold is ' + (threshold / 100)
+ '. Fund the account before the next payout run.',
});
}
}
}
// Scheduled payout pre-check
async function prePayoutBalanceCheck(scheduledPayouts) {
var byCurrency = {};
// Group payouts by currency
for (var i = 0; i < scheduledPayouts.length; i++) {
var p = scheduledPayouts[i];
if (!byCurrency[p.currency]) {
byCurrency[p.currency] = [];
}
byCurrency[p.currency].push(p);
}
var issues = [];
for (var currency in byCurrency) {
var validation = await validateBatchPayout(byCurrency[currency], currency);
if (!validation.canAfford) {
issues.push({
currency: currency,
shortfall: validation.shortfall / 100,
payoutCount: validation.payoutCount,
suggestion: validation.suggestion,
});
}
}
if (issues.length > 0) {
await sendAlert({
type: 'payout_funding_needed',
issues: issues,
message: 'Upcoming payout run has funding gaps in '
+ issues.length + ' currencies.',
});
}
return issues;
}
Run the pre-payout check 4-6 hours before your scheduled payout window. This gives your finance team time to fund the balance through a bank transfer or by adjusting the payout queue. Surprises at payout time lead to stressed operations teams and delayed vendor payments.
If you operate in multiple Paystack markets (Nigeria, Kenya, Ghana), you maintain separate balances per currency. NGN payments go to your NGN balance, KES payments to your KES balance. You cannot transfer NGN from a KES balance or vice versa.
This means your balance check must match the currency of the transfer. A common bug: checking the NGN balance before initiating a KES transfer. The check passes (your NGN balance is healthy), but the transfer fails because your KES balance is empty.
async function validateTransferCurrency(amount, currency, recipientType) {
var balance = await getAvailableBalance(currency);
if (balance.total === 0) {
return {
valid: false,
error: 'No ' + currency + ' balance available. '
+ 'Ensure you have received payments in ' + currency
+ ' or funded your ' + currency + ' balance.',
};
}
if (balance.total < amount) {
return {
valid: false,
error: currency + ' balance (' + (balance.total / 100)
+ ') is insufficient for transfer of ' + (amount / 100) + '.',
};
}
return { valid: true };
}
For the broader guide on how transfers work across currencies and countries, see the transfers and payouts complete guide.
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