Restricting Payment Channels at Checkout in Paystack
To restrict payment channels on Paystack checkout, pass a channels array when initializing the transaction. Valid values are "card", "bank", "ussd", "mobile_money", "bank_transfer", "qr", and "apple_pay". Only the channels you list will appear on the checkout page. Omit the channels parameter to show all available channels.
How the Channels Parameter Works
When you initialize a transaction, you can include a channels array in the request body. Paystack checkout will only show the channels you list. If you omit the parameter entirely, Paystack shows all channels available for the transaction's currency and your account.
// Only show card and bank transfer
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: 250000, // 2,500 NGN
currency: 'NGN',
channels: ['card', 'bank_transfer'],
}),
});
The valid channel values are:
card- Visa, Mastercard, Vervebank- Pay with bank (different from bank_transfer)ussd- USSD banking codesmobile_money- MTN MoMo, AirtelTigo, Vodafone Cashbank_transfer- Transfer to a temporary bank accountqr- QR code scanningapple_pay- Apple Pay on supported devices
Note the difference between bank and bank_transfer. The bank channel refers to "Pay with Bank" (online banking), while bank_transfer is the transfer-to-account method where the customer manually transfers from their banking app.
When to Restrict Channels
Scenario 1: You need instant confirmation
Card payments confirm in seconds. Bank transfers, USSD, and mobile money can take anywhere from 30 seconds to several minutes. If your product requires immediate confirmation (a concert ticket about to sell out, a flash sale item, a time-limited offer), restrict to ['card', 'apple_pay'] so the payment confirms instantly.
Scenario 2: The amount is too small for some channels
Some USSD channels have minimum transaction amounts. Very small transactions (under 100 NGN, for example) may not work on all USSD banks. For micro-transactions, card or bank transfer may be more reliable.
Scenario 3: The amount is too large for some channels
USSD channels typically have per-transaction limits (which vary by bank). For transactions above a certain threshold, restrict to card and bank transfer to avoid the customer selecting USSD only to find their bank does not support that amount.
Scenario 4: Simpler customer support
Each payment channel has unique edge cases. Bank transfers can have delays. USSD codes can be entered incorrectly. Mobile money prompts can time out. If your support team is small, starting with fewer channels reduces the variety of issues they need to handle.
Scenario 5: Different channels for different products
A digital product download might use card-only for instant delivery. A physical product with a 2-day shipping window can accept any channel because the delay from bank transfer or USSD does not matter.
Dynamic Channel Selection
You can compute the channels list per transaction based on business rules. Here is a pattern:
function getChannelsForTransaction(amount, currency, productType) {
// Start with all channels for the currency
var channelMap = {
NGN: ['card', 'bank_transfer', 'ussd', 'qr', 'apple_pay'],
GHS: ['card', 'mobile_money', 'apple_pay'],
ZAR: ['card', 'apple_pay'],
KES: ['card', 'apple_pay'],
};
var channels = channelMap[currency] || ['card'];
// Digital products: card only for instant delivery
if (productType === 'digital') {
return ['card', 'apple_pay'];
}
// High-value transactions: remove USSD (bank limits)
if (currency === 'NGN' && amount > 10000000) { // Over 100,000 NGN
channels = channels.filter(function(ch) { return ch !== 'ussd'; });
}
// Very low amounts: card and bank transfer only
if (amount < 10000) { // Under 100 NGN
return ['card', 'bank_transfer'];
}
return channels;
}
// Usage in your pay endpoint
app.post('/api/pay', async function(req, res) {
var channels = getChannelsForTransaction(
req.body.amount * 100,
req.body.currency,
req.body.productType
);
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: req.body.amount * 100,
currency: req.body.currency,
channels: channels,
}),
});
var data = await response.json();
res.json(data.data);
});
Channel and Currency Compatibility
If you pass a channel that is not available for the transaction's currency, Paystack silently ignores it. This can lead to a checkout with no payment options if all the channels you specified are unavailable.
// This produces a checkout with no payment options!
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,
currency: 'GHS',
channels: ['bank_transfer', 'ussd'], // Neither is available for GHS
}),
});
// Checkout opens but customer has no way to pay
To prevent this, validate channels against the currency before sending to Paystack:
var validChannelsByCurrency = {
NGN: ['card', 'bank', 'ussd', 'bank_transfer', 'qr', 'apple_pay'],
GHS: ['card', 'mobile_money', 'apple_pay'],
ZAR: ['card', 'apple_pay'],
KES: ['card', 'apple_pay'],
USD: ['card', 'apple_pay'],
};
function filterValidChannels(channels, currency) {
var valid = validChannelsByCurrency[currency] || ['card'];
var filtered = channels.filter(function(ch) {
return valid.indexOf(ch) !== -1;
});
// Fallback to card if no valid channels remain
if (filtered.length === 0) {
return ['card'];
}
return filtered;
}
This guarantees the customer always has at least one payment option.
Channels in the Charge API
The channels parameter is for Initialize Transaction (the hosted checkout). If you use the Charge API, channel selection is implicit: the channel depends on what payment data you include in the charge request.
- Send a
bankobject with account details: bank transfer - Send a
ussdobject with the bank type: USSD payment - Send a
mobile_moneyobject with the phone number and provider: mobile money - Send an
authorization_code: card charge on a saved card
With the Charge API, you control the channel by what data you send, not by a channels array. This gives you full control but requires you to build the payment method selection UI yourself.
For most applications, Initialize Transaction with the channels parameter is the simpler and safer approach. Use the Charge API only when you need a fully custom checkout experience. See Charge API for custom checkout for more detail.
Testing Channel Restrictions
Test each channel configuration in Paystack test mode before deploying to production. Here is what to check:
- Initialize a transaction with your restricted channels and open the checkout page
- Verify that only the channels you specified appear
- Complete a test payment on each channel to confirm it works end to end
- Test with invalid channel combinations (like mobile_money for NGN) to see how the checkout behaves
- Test with an empty channels array to understand the error behavior
Paystack test mode simulates different channels. Card payments use test card numbers. Bank transfers use test accounts. Not all channels may be fully testable in test mode (check the Paystack documentation for current test mode capabilities).
After testing, monitor your checkout conversion rate after deploying channel restrictions. If conversion drops, you may have removed a channel that many customers prefer. Analytics on which channels customers use (available in your Paystack dashboard) can guide your decision.
Key Takeaways
- ✓Pass a channels array to /transaction/initialize to control which payment methods appear on the checkout page.
- ✓Valid values: "card", "bank", "ussd", "mobile_money", "bank_transfer", "qr", "apple_pay". Only channels available for the transaction currency will actually appear.
- ✓Restricting to card-only gives you instant payment confirmation. Bank transfer, USSD, and mobile money can take minutes.
- ✓You can vary channels per transaction. High-value orders might offer card and bank transfer. Low-value orders might include USSD.
- ✓If you pass a channel that is not available for the currency, it is silently ignored. If all passed channels are unavailable, the checkout has no payment options.
- ✓Start with all channels (omit the parameter) and restrict only when you have a specific reason. More options means more customers can pay.
Frequently Asked Questions
- What happens if I pass an empty channels array?
- If you pass an empty array, the checkout may show no payment options or Paystack may return all default channels. The behavior is not well-documented, so it is safest to either omit the parameter (to show all channels) or pass at least one valid channel.
- Can I restrict channels in the Paystack dashboard instead of the API?
- The Paystack dashboard allows you to enable or disable certain payment channels globally for your account. The channels parameter in the API lets you further restrict per transaction. The two work together: a channel must be enabled on your account AND included in the channels parameter to appear at checkout.
- Does restricting channels affect transaction fees?
- No. The transaction fee is based on the channel the customer actually uses to pay, not on which channels you offered. Restricting channels does not change the fee structure.
- Can I add channels to a transaction after initialization?
- No. The channels are set at initialization time and cannot be changed for an existing transaction. If you need different channels, initialize a new transaction (you can use the same reference if the first was not completed).
- Should I always restrict channels or leave the default?
- Leave the default (all available channels) unless you have a specific reason to restrict. More payment options means more customers can pay using their preferred method. Only restrict when instant confirmation, amount limits, or support capacity require it.
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