To set the currency for a Paystack transaction, pass the currency field (e.g., "NGN", "GHS", "ZAR", "KES", "USD") when initializing. If omitted, Paystack defaults to your account primary currency. Each currency is tied to the country where your Paystack business is registered, and available payment channels vary by currency.
Paystack processes payments in five currencies, each tied to a specific market:
The currencies available on your account depend on where your business is registered and what Paystack has enabled. A Nigerian-registered business gets NGN by default. To accept GHS, you would typically need a Ghana-registered entity or request multi-currency activation from Paystack. Check your dashboard under Settings to see which currencies are active on your account.
Pass the currency field when calling /transaction/initialize:
// Initialize a transaction in GHS
var response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'customer@example.com',
amount: 50000, // 500 GHS in pesewas
currency: 'GHS', // Explicitly set the currency
callback_url: 'https://yoursite.com/payment/callback',
}),
});
If you omit the currency field, Paystack uses your account's primary currency. For a Nigerian account, this means NGN. If your customer selected GHS and you forgot to pass it, the customer gets charged in Naira. The amount 50000 becomes 500 NGN instead of 500 GHS. This mismatch is silent from the API's perspective. The transaction initializes and processes without error. You only discover the problem when the customer complains or when reconciliation reveals the discrepancy.
Rule: always pass currency explicitly, even if your application only supports one currency today. It makes your code self-documenting and protects you if you add currencies later.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Not all payment channels are available in all currencies. Here is the general landscape:
| Channel | NGN | GHS | ZAR | KES | USD |
|---|---|---|---|---|---|
| Card (Visa/Mastercard) | Yes | Yes | Yes | Yes | Yes |
| Bank Transfer | Yes | No | No | No | No |
| USSD | Yes | No | No | No | No |
| Mobile Money | No | Yes | No | No | No |
| QR | Yes | No | No | No | No |
| Dedicated Virtual Accounts | Yes | No | No | No | No |
| Apple Pay | Yes | Yes | Yes | Yes | Yes |
This table reflects the general availability. Paystack may enable or restrict channels based on your account type and business category. Always check your dashboard or contact Paystack support for your specific account's channel availability.
When you restrict channels with the channels parameter, only pass channels that are available for the transaction's currency. If you pass channels: ['mobile_money'] for an NGN transaction, the checkout will have no valid payment option and the customer will be stuck. For more on channel restriction, see restricting payment channels at checkout.
If your application serves customers in multiple countries, you need to select the right currency based on the customer's location or preference. Here is a pattern that handles this cleanly:
// Currency configuration by country
var currencyConfig = {
NG: { code: 'NGN', channels: ['card', 'bank_transfer', 'ussd'] },
GH: { code: 'GHS', channels: ['card', 'mobile_money'] },
ZA: { code: 'ZAR', channels: ['card'] },
KE: { code: 'KES', channels: ['card'] },
};
app.post('/api/pay', async function(req, res) {
var country = req.body.country; // 'NG', 'GH', 'ZA', 'KE'
var config = currencyConfig[country];
if (!config) {
return res.status(400).json({ error: 'Unsupported country' });
}
var response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: req.body.email,
amount: Math.round(req.body.displayAmount * 100),
currency: config.code,
channels: config.channels,
callback_url: 'https://yoursite.com/payment/callback',
}),
});
var data = await response.json();
res.json(data.data);
});
This pattern ensures the currency and available channels always match. A Ghanaian customer sees card and mobile money options. A Nigerian customer sees card, bank transfer, and USSD. No one sees a channel that does not work for their currency.
When you verify a transaction, check the currency field alongside the amount and status:
app.get('/payment/callback', async function(req, res) {
var reference = req.query.reference;
var order = await db.orders.findByReference(reference);
var verifyResponse = await fetch(
'https://api.paystack.co/transaction/verify/' + reference,
{
headers: {
Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
},
}
);
var data = await verifyResponse.json();
var tx = data.data;
// Verify ALL three: status, amount, and currency
var amountMatch = tx.amount === order.amount_minor;
var currencyMatch = tx.currency === order.currency;
var statusMatch = tx.status === 'success';
if (statusMatch && amountMatch && currencyMatch) {
await fulfillOrder(order.id);
res.redirect('/order/success');
} else {
console.log('Verification failed:');
console.log(' Status: ' + tx.status + ' (expected success)');
console.log(' Amount: ' + tx.amount + ' (expected ' + order.amount_minor + ')');
console.log(' Currency: ' + tx.currency + ' (expected ' + order.currency + ')');
res.redirect('/order/failed');
}
});
Why check the currency? Because 500000 kobo (5,000 NGN) and 500000 cents (5,000 USD) are very different amounts. If an attacker manages to initialize a transaction in NGN for a price listed in USD, the amount integer matches but the real value is off by orders of magnitude. The currency check catches this.
The same applies to webhook handlers. The charge.success webhook payload includes both amount and currency. Verify both.
Settlement timelines and procedures differ by currency and market. While the exact details depend on your account configuration and Paystack's current policies (which change over time), here are general patterns engineers should know:
From an engineering perspective, the key thing to understand is that settlement is not the same as payment success. A payment can succeed (charge.success) and the money can take hours or days to reach your bank account. Your system should grant value to the customer when the payment succeeds, not when the settlement arrives. For more on settlement timelines, see settlement timelines and what engineers should assume.
Mistake 1: Hardcoding NGN when your app supports multiple currencies
Early in development, you might hardcode currency: 'NGN' and forget to make it dynamic. When you launch in Ghana, Ghanaian customers get charged in Naira. Always pull the currency from the order or customer context.
Mistake 2: Displaying one currency and charging another
Your frontend shows "GH¢ 500" but your server sends currency: 'NGN', amount: 50000. The customer pays 500 NGN instead of 500 GHS. The amount integer looks the same but the currencies are different. Always derive the currency from the same source as the display price.
Mistake 3: Assuming all channels work for all currencies
If you pass channels: ['bank_transfer'] for a GHS transaction, the checkout shows no payment options. Bank transfer is only available for NGN. Check channel availability for each currency before restricting channels.
Mistake 4: Not handling currency in refunds
Refunds are processed in the same currency as the original transaction. You cannot charge in NGN and refund in GHS. The refund amount is in the same smallest unit as the charge.
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