Bonaventure OgetoBy Bonaventure Ogeto|

Currency Handling in Paystack: NGN, GHS, ZAR, KES and USD

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.

Supported Currencies Overview

Paystack processes payments in five currencies, each tied to a specific market:

  • NGN (Nigerian Naira): The original and most feature-complete Paystack market. Supports card, bank transfer, USSD, QR, and dedicated virtual accounts.
  • GHS (Ghanaian Cedi): Supports card and mobile money. Mobile money is the dominant payment method in Ghana.
  • ZAR (South African Rand): Supports card payments. The South African market has high card adoption.
  • KES (Kenyan Shilling): Supports card and M-Pesa integration. M-Pesa is the dominant payment channel in Kenya.
  • USD (US Dollar): Available on some Paystack accounts, primarily for international collections. Payment channels are typically card-only.

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.

Setting the Currency in a Transaction

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.

Payment Channels by Currency

Not all payment channels are available in all currencies. Here is the general landscape:

ChannelNGNGHSZARKESUSD
Card (Visa/Mastercard)YesYesYesYesYes
Bank TransferYesNoNoNoNo
USSDYesNoNoNoNo
Mobile MoneyNoYesNoNoNo
QRYesNoNoNoNo
Dedicated Virtual AccountsYesNoNoNoNo
Apple PayYesYesYesYesYes

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.

Currency-Aware Transaction Initialization

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.

Verifying Currency in Responses

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 Behavior by Currency

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:

  • NGN settlements: Typically settle to your Nigerian bank account. The standard cycle is next business day (T+1) for most businesses.
  • GHS settlements: Settle to your Ghanaian bank account. Settlement cycles may differ from NGN.
  • ZAR settlements: Settle to your South African bank account.
  • KES settlements: Settle to your Kenyan bank account.
  • USD settlements: Settlement details depend on your account setup. Some accounts receive USD settlements; others have USD converted to local currency before settlement.

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.

Common Currency Mistakes

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.

Stay Up to Date

Paystack continues to expand into new markets and may add new currencies or payment channels. We update these guides as things change.

Sign up for the McTaba newsletter to stay informed about Paystack currency and market updates.

Key Takeaways

  • Paystack supports five currencies: NGN, GHS, ZAR, KES, and USD. The currencies available to you depend on your business registration country.
  • Always pass the currency parameter explicitly when initializing a transaction. If omitted, Paystack defaults to your account primary currency, which can cause wrong charges for multi-currency setups.
  • Payment channels vary by currency. Card payments work across all currencies. Bank transfers, USSD, and mobile money have country-specific availability.
  • Amounts are always in the smallest currency unit (kobo, pesewas, cents) regardless of which currency you use. The multiplier is 100 for all Paystack currencies.
  • Currency and amount verification during transaction verification is essential. Check both the amount and the currency code match what you expected.
  • You cannot convert between currencies through Paystack. If you accept multiple currencies, each transaction is in a single currency.

Frequently Asked Questions

Can I accept payments in a currency my Paystack account is not registered for?
Generally no. Your available currencies are tied to your business registration and what Paystack has enabled on your account. You may need to register a business entity in the target country or request multi-currency activation from Paystack support.
Does Paystack handle currency conversion?
Paystack does not convert between currencies during a transaction. If a customer pays in GHS, you receive GHS. If you need to convert that to another currency, you handle that through your bank or a separate forex process.
Can I charge a Nigerian customer in USD?
If your account supports USD transactions, you can initialize a transaction in USD regardless of where the customer is located. However, the customer's bank may apply cross-border fees or conversion charges on their end. For Nigerian customers, charging in NGN usually provides a better experience.
What currency does the webhook payload use?
The webhook payload includes the currency field matching whatever currency the transaction was initialized in. The amount is in the smallest unit of that currency. Both are included in the data object of the webhook event.

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