Paystack settles funds to your bank account on a rolling schedule, not instantly. In Nigeria, the typical settlement cycle is T+1 (next business day). Ghana, Kenya, and South Africa have their own schedules. Your application should grant value after payment verification (seconds), not after settlement (hours or days). Track payment events and settlement events as two separate things in your system.
When a customer pays through Paystack, the money does not teleport from their bank account to yours. Here is what happens after a successful charge:
Steps 1 through 3 happen within seconds to minutes. Step 4 happens on a schedule, usually the next business day. The gap between "customer paid" and "money in your bank" is the settlement window.
For your application logic, the distinction matters because you need to make two separate decisions:
Paystack operates in multiple markets, and each one has a different settlement rhythm.
Nigeria (NGN)
The standard settlement cycle is T+1, meaning funds collected today are settled to your bank account on the next business day. A charge captured on Monday settles on Tuesday. A charge captured on Friday settles on the following Monday. Public holidays shift settlements to the next working day.
Some Nigerian merchants on higher-volume tiers may have different settlement schedules. Check your Paystack dashboard under Settings > Settlement for your specific schedule.
Ghana (GHS)
Settlement cycles for Ghana vary based on your account configuration and the payment channel used. Mobile money settlements may follow a different schedule than card payments. Check your dashboard for the exact settlement window.
South Africa (ZAR)
South African settlement schedules depend on your agreement with Paystack. The standard window is typically a few business days. Card payments and EFT payments may settle on different schedules.
Kenya (KES)
Kenyan settlement cycles are configured per account. Mobile money (M-Pesa) settlements may differ from card settlement schedules.
The key engineering takeaway: never hardcode settlement assumptions. Your application should not assume "money arrives tomorrow." Instead, use the settlement API to check actual settlement status.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Paystack provides a settlement API that lets you list settlement batches, see which transactions were included, and check settlement status.
// List recent settlements
const response = await fetch('https://api.paystack.co/settlement', {
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
});
const data = await response.json();
// data.data is an array of settlement objects
// Each settlement contains:
// - id: settlement ID
// - total_amount: gross amount before fees (in kobo/pesewas)
// - total_fees: total fees deducted
// - net_amount: what lands in your bank (total_amount - total_fees)
// - settlement_date: when it was sent to your bank
// - status: "success", "pending", "failed"
To see which transactions were included in a specific settlement:
// Get transactions for a specific settlement
const settlementId = 12345;
const txResponse = await fetch(
'https://api.paystack.co/settlement/' + settlementId + '/transactions',
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
const txData = await txResponse.json();
// txData.data is an array of transactions included in this settlement
This data is essential for reconciliation. Your accounting system should match each settlement batch from Paystack against the actual bank credit. If the numbers do not match, you know something needs investigation.
Here are the engineering patterns that keep settlement timing from causing problems in your application.
Pattern 1: Separate "paid" from "settled" in your database
// Order schema (simplified)
// status: 'pending' | 'paid' | 'settled' | 'refunded'
// paid_at: timestamp when charge was verified
// settled_at: timestamp when settlement was confirmed (nullable)
// paystack_reference: the transaction reference
// settlement_id: the settlement batch ID (nullable)
// When webhook arrives and payment is verified:
await db.query(
'UPDATE orders SET status = $1, paid_at = $2 WHERE paystack_reference = $3',
['paid', new Date(), reference]
);
// When you reconcile settlements (daily job):
const settlements = await fetchPaystackSettlements();
for (const settlement of settlements) {
const transactions = await fetchSettlementTransactions(settlement.id);
for (const tx of transactions) {
await db.query(
'UPDATE orders SET status = $1, settled_at = $2, settlement_id = $3 WHERE paystack_reference = $4',
['settled', settlement.settlement_date, settlement.id, tx.reference]
);
}
}
Pattern 2: Do not block fulfillment on settlement
When a customer pays for a digital product, deliver it after payment verification, not after settlement. The customer should not care about your banking schedule. They paid, they get the product.
Pattern 3: Reconcile daily, not real-time
Run a daily reconciliation job that pulls settlement data from Paystack and matches it against your orders. This catches discrepancies early without adding complexity to your real-time payment flow.
Pattern 4: Track refunds separately from settlements
If you issue a refund after a transaction has already been settled, the refund amount is deducted from a future settlement. Your reconciliation logic needs to handle this: a settlement batch might have a lower net amount than expected because refunds were deducted.
These scenarios are uncommon but real. Each one has tripped up a production system.
Public holiday shifts: A charge captured on a Thursday before a Friday public holiday settles on the following Monday. If your system expects T+1 and alerts on "settlement overdue after 2 days," you will get false alarms around holidays. Account for the business day calendar in your reconciliation logic, or simply do not set tight settlement deadlines.
Failed settlement: Paystack tried to settle to your bank account, but the bank rejected it. Reasons: account number changed, account frozen, bank downtime. Paystack retries on the next cycle. Your dashboard shows the settlement as "failed." If you have automated alerts on settlement status, include "failed" as a critical alert.
Split settlements: If you use Paystack subaccounts for marketplace splits, each subaccount has its own settlement schedule. The main account and subaccounts may settle on different days. Track settlements per subaccount, not just for your main account.
Currency conversion delays: If you accept payments in a currency different from your settlement currency, the conversion adds time. Multi-currency transactions may settle a day later than same-currency transactions.
Settlement amount mismatch: The settled amount is always less than the charged amount because of fees. If your reconciliation compares charge amounts to settlement amounts without accounting for fees, every single settlement will look wrong. Always compare against the net amount (charge amount minus fees).
Your Paystack dashboard shows two numbers that confuse most people the first time they see them.
Available balance: Money from successful charges that has not yet been settled to your bank. This balance grows with each successful payment and decreases with each settlement cycle.
Ledger balance: The total of all charges minus all settlements, refunds, and fees. This is the running total of everything in your Paystack account.
You can check your balance through the API:
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, one per currency
// Each contains:
// - currency: "NGN", "GHS", "ZAR", "KES"
// - balance: available balance in kobo/pesewas/cents
For a deeper look at querying and interpreting these numbers, see Balance and Settlement APIs explained.
The available balance is useful for transfers. If you use Paystack Transfers to pay out to vendors or customers, the available balance is where the money comes from. You cannot transfer more than your available balance. If you try, the transfer fails.
Paystack fires settlement-related webhook events that you can listen for:
transfer.success: Fires when Paystack successfully settles funds to your bank account. (Note: this is the same event type used for Paystack Transfers. Settlement and transfer payouts share the notification mechanism.)transfer.failed: Fires when a settlement attempt fails.You can also set up email notifications on the Paystack dashboard to alert your finance team when settlements happen.
For most applications, polling the settlement API once a day in a background job is simpler and more reliable than relying on settlement webhooks. Webhooks can fail, arrive out of order, or be delayed. A daily reconciliation job with the settlement API gives you a consistent view of all settlements.
// Daily reconciliation job (run via cron)
async function dailySettlementReconciliation() {
const today = new Date().toISOString().split('T')[0];
const yesterday = new Date(Date.now() - 86400000).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 data = await response.json();
for (const settlement of data.data) {
console.log(
'Settlement ' + settlement.id +
': net ' + (settlement.net_amount / 100) +
' ' + settlement.currency +
' on ' + settlement.settlement_date
);
// Match against bank statement entries
// Flag any mismatches for manual review
}
}
For the complete accept payments workflow, including webhook setup, see the accept payments 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