Paystack Balance and Settlement APIs Explained
The Paystack Balance API returns your available and ledger balance per currency. The Settlement API lists settlement batches with gross amount, fees, net amount, settlement date, and status. Together they let you build automated reconciliation that matches Paystack payouts against your bank statement entries.
The Balance API: What You Have Right Now
The balance endpoint tells you how much money is sitting in your Paystack account at this moment. It is a read-only snapshot.
const response = await fetch('https://api.paystack.co/balance', {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
// data.data is an array of balance objects
// Example response:
// [
// { "currency": "NGN", "balance": 15000000 },
// { "currency": "GHS", "balance": 500000 }
// ]
The balance value is in the smallest currency unit: kobo for NGN, pesewas for GHS, cents for ZAR and KES. To get the human-readable amount, divide by 100. So 15000000 kobo is NGN 150,000.
If you accept payments in multiple currencies, you get one balance entry per currency. A Nigerian business that also accepts GHS payments will see two entries.
What the balance includes:
- Successful charges that have not yet been settled
- Pending transfers that have not yet been processed
- Refunds that have been initiated but not yet deducted from the next settlement
What the balance does not include:
- Charges that are still in progress (pending or abandoned)
- Money that has already been settled to your bank account
The balance is useful for two things: knowing if you have enough funds to initiate a transfer, and monitoring your account health at a glance.
Listing Settlements: The Settlement API
The settlement endpoint lists every settlement batch Paystack has sent (or attempted to send) to your bank account.
// List settlements with optional date filters
const response = await fetch(
'https://api.paystack.co/settlement?from=2026-07-01&to=2026-07-22',
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const data = await response.json();
// data.data is an array of settlement objects
// Each object:
// {
// "id": 12345,
// "total_amount": 5000000, // Gross charges included (kobo)
// "total_fees": 75000, // Fees deducted (kobo)
// "net_amount": 4925000, // What was sent to your bank (kobo)
// "currency": "NGN",
// "status": "success", // or "pending" or "failed"
// "settlement_date": "2026-07-22T00:00:00.000Z"
// }
Key fields in each settlement object:
- total_amount: The sum of all charges included in this settlement, before fee deduction. This is the gross number.
- total_fees: The total processing fees Paystack deducted from this batch.
- net_amount: total_amount minus total_fees. This is the amount that was actually sent to your bank. This is the number you should match against your bank statement.
- status: "success" means the money was sent. "pending" means the settlement is scheduled but not yet processed. "failed" means the bank rejected it and Paystack will retry.
- settlement_date: The date the settlement was processed or is scheduled for.
Pagination: The settlement endpoint returns results in pages. Use the perPage and page query parameters to navigate through large result sets.
Drilling Into Settlement Transactions
When you find a settlement whose net amount does not match your bank credit, you need to see the individual transactions that were included. The settlement transactions endpoint gives you this breakdown.
// Get transactions included in settlement #12345
const response = await fetch(
'https://api.paystack.co/settlement/12345/transactions',
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const data = await response.json();
// data.data is an array of transactions
// Each transaction contains:
// - reference: your transaction reference
// - amount: charge amount in kobo
// - fees: fee deducted for this transaction
// - customer: { email, ... }
// - created_at: when the charge happened
This endpoint lets you trace every kobo. If your bank statement shows a credit of NGN 49,250 but your records say you should have received NGN 50,000, you can pull the settlement transactions, sum the fees, and find the exact source of the difference.
Common reasons for discrepancies:
- Refunds deducted from settlement: If you refunded a transaction between the charge and the settlement, the refund amount is subtracted from the settlement batch.
- Chargebacks deducted: If a customer filed a chargeback and the dispute was resolved against you, the chargeback amount is deducted from a future settlement.
- Fee calculation differences: Fee caps, percentage tiers, and channel-specific rates mean that total fees are not simply "flat rate times number of transactions."
Building a Reconciliation System
Here is a practical reconciliation workflow that runs as a daily background job.
async function reconcileSettlements() {
// 1. Fetch yesterday's settlements from Paystack
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
const today = new Date().toISOString().split('T')[0];
const response = await fetch(
'https://api.paystack.co/settlement?from=' + yesterday + '&to=' + today,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const settlements = await response.json();
for (const settlement of settlements.data) {
// 2. Check if we already reconciled this settlement
const existing = await db.query(
'SELECT id FROM reconciled_settlements WHERE paystack_settlement_id = $1',
[settlement.id]
);
if (existing.rows.length > 0) continue;
// 3. Fetch the transactions in this settlement
const txResponse = await fetch(
'https://api.paystack.co/settlement/' + settlement.id + '/transactions',
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const transactions = await txResponse.json();
// 4. Cross-check against our orders
let matchedCount = 0;
let unmatchedRefs = [];
for (const tx of transactions.data) {
const order = await db.query(
'SELECT id FROM orders WHERE paystack_reference = $1 AND status = $2',
[tx.reference, 'paid']
);
if (order.rows.length > 0) {
await db.query(
'UPDATE orders SET status = $1, settled_at = $2 WHERE paystack_reference = $3',
['settled', settlement.settlement_date, tx.reference]
);
matchedCount++;
} else {
unmatchedRefs.push(tx.reference);
}
}
// 5. Record the reconciliation result
await db.query(
'INSERT INTO reconciled_settlements (paystack_settlement_id, net_amount, matched, unmatched, reconciled_at) VALUES ($1, $2, $3, $4, $5)',
[settlement.id, settlement.net_amount, matchedCount, unmatchedRefs.length, new Date()]
);
// 6. Alert on unmatched transactions
if (unmatchedRefs.length > 0) {
console.log('Unmatched transactions in settlement ' + settlement.id + ': ' + unmatchedRefs.join(', '));
// Send alert to finance team
}
}
}
Run this job daily after the settlement window closes. In Nigeria with T+1 settlement, run it in the morning to catch the previous day's settlement.
Using Balance Data for Transfer Decisions
If you use Paystack Transfers to pay vendors, contractors, or customers, your available balance is the funding source. Before initiating a transfer, check that you have enough balance.
async function canTransfer(amountInKobo, currency) {
const response = await fetch('https://api.paystack.co/balance', {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
const balanceEntry = data.data.find(function(b) { return b.currency === currency; });
if (!balanceEntry) {
return { canTransfer: false, reason: 'No balance in ' + currency };
}
if (balanceEntry.balance < amountInKobo) {
return {
canTransfer: false,
reason: 'Insufficient balance. Available: ' + (balanceEntry.balance / 100) + ' ' + currency + ', Required: ' + (amountInKobo / 100) + ' ' + currency,
};
}
return { canTransfer: true, available: balanceEntry.balance };
}
This check prevents transfer failures due to insufficient balance. Paystack rejects transfers that exceed your available balance, but checking beforehand gives you a chance to show a meaningful error message or queue the transfer for later.
Keep in mind that the balance can change between your check and the actual transfer request. If multiple transfer requests happen concurrently, you could pass the balance check but still fail the transfer. Handle the insufficient balance error from the Transfer API as a fallback.
Multi-Currency Balances
If your Paystack account accepts payments in multiple currencies, the Balance API returns a separate entry for each currency. Settlements happen per currency too. A GHS settlement goes to your Ghana bank account, and an NGN settlement goes to your Nigerian bank account.
When building financial dashboards or reports, display each currency balance separately. Do not convert and sum them. Exchange rates fluctuate, and your accounting system needs to track each currency independently.
async function displayBalances() {
const response = await fetch('https://api.paystack.co/balance', {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
for (const entry of data.data) {
const amount = entry.balance / 100;
console.log(entry.currency + ': ' + amount.toLocaleString());
}
// Output:
// NGN: 150,000
// GHS: 5,000
}
For settlement reconciliation across currencies, run separate reconciliation jobs for each currency. Each currency has its own settlement schedule, fee structure, and bank account destination.
For the broader picture of how settlement fits into the accept payments workflow, see the complete accept payments guide. For settlement timing specifics, see settlement timelines and what engineers should assume.
Common Mistakes with Balance and Settlement Data
These mistakes show up repeatedly in production integrations:
- Comparing gross amounts to bank credits. The bank credit is the net amount (after fees). If you compare the settlement total_amount against your bank statement, every single settlement will show a discrepancy. Always use net_amount.
- Ignoring failed settlements. A settlement with status "failed" means Paystack tried to pay you and the bank rejected it. The money is still in your Paystack balance. It will be retried on the next cycle. If you only track "success" settlements, you will think money is missing when it is actually queued for retry.
- Caching balance data. The balance changes with every charge and every settlement. Do not cache it for more than a few seconds. If you need to display the balance on a dashboard, fetch it fresh each time the page loads.
- Not paginating settlement queries. If your account processes many transactions, a single settlement API call might not return all results. Always check for pagination and iterate through all pages.
- Hardcoding fee calculations. Fees vary by channel, volume tier, and country. Never calculate expected fees in your application. Read the actual fee from the settlement data.
Key Takeaways
- ✓The Balance API endpoint (/balance) returns an array of balance objects, one per currency. Each object contains the available balance (funds ready for settlement or transfer) in the smallest currency unit (kobo, pesewas, cents).
- ✓The Settlement API endpoint (/settlement) lists settlement batches. Each batch contains the total gross amount, total fees deducted, net amount sent to your bank, settlement date, and status (success, pending, or failed).
- ✓Available balance is money from successful charges that has not yet been settled. It is the pool from which Paystack pays you on the next settlement cycle and from which you can initiate transfers.
- ✓The settlement net amount is always less than the gross amount because Paystack deducts processing fees. Your reconciliation logic must compare net amounts, not gross amounts, against bank credits.
- ✓You can filter settlements by date range, status, and subaccount. This makes it possible to reconcile daily, weekly, or monthly depending on your accounting needs.
- ✓Use the settlement transactions endpoint to see which individual transactions were included in a specific settlement batch. This is essential for tracing discrepancies.
Frequently Asked Questions
- What is the difference between available balance and ledger balance on Paystack?
- The available balance is money from successful charges that has not yet been settled or transferred. It is the amount you can use for transfers right now. The ledger balance is the cumulative total of all charges, settlements, refunds, and transfers. The Balance API typically returns the available balance. Check your Paystack dashboard for the ledger balance breakdown.
- How often should I check my Paystack balance via the API?
- It depends on your use case. If you are displaying the balance on an admin dashboard, fetch it each time the page loads. If you are checking before initiating transfers, fetch it right before each transfer. Do not poll the balance endpoint continuously. There is no webhook for balance changes, so a periodic check (or on-demand fetch) is the right approach.
- Can I get settlement data for a specific subaccount?
- Yes. If you use Paystack subaccounts for marketplace splits, you can filter settlement data by subaccount. Pass the subaccount parameter when querying the settlement endpoint to see settlements for a specific subaccount.
- Why does my settlement show zero fees for some transactions?
- Some transaction types or promotional periods may have zero or reduced fees. Also, if a fee cap applies and you have already hit the cap for that transaction, the individual fee may be lower than expected. Check with Paystack support if you see consistently unexpected fee amounts.
- How do refunds affect my settlement?
- If you issue a refund after the transaction has been settled, the refund amount is deducted from a future settlement. If you issue a refund before settlement, the refunded transaction is excluded from the settlement batch entirely. Either way, refunds reduce the net amount you receive.
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