When Flutterwave is the right choice
If your product only needs to collect payments in Nigeria, Paystack is simpler. Flutterwave earns its place when you need multiple African countries in the same integration — M-Pesa in Kenya, mobile money in Ghana, EFT in South Africa, cards everywhere. 30+ currencies, one API. If your roadmap includes pan-African expansion, building on Flutterwave from the start is less painful than migrating to it after you've already shipped Paystack.
How Flutterwave payments work
Same core pattern as any hosted checkout: your server creates a payment with the Flutterwave API, gets back a hosted payment link, redirects the user. They pay using whichever local method is available in their country. Flutterwave redirects back to your URL with a transaction ID. You verify that ID server-side before releasing anything. The payment methods shown — cards, M-Pesa, mobile money, bank transfer — are determined automatically by the user's currency and country.
1. Get your API keys
From the Flutterwave dashboard under Settings > API. The secret key lives on the server only:
# Test keys FLUTTERWAVE_SECRET_KEY=FLWSECK_TEST-... FLUTTERWAVE_PUBLIC_KEY=FLWPUBK_TEST-... FLW_WEBHOOK_HASH=your_custom_hash # Set this in FLW dashboard > Webhooks
2. Create a payment (server-side)
A plain API call — works in any language. Pass currency to control which payment methods Flutterwave shows:
async function createFlutterwavePayment({ email, name, amount, currency = 'NGN' }) {
const tx_ref = `order-${Date.now()}-${Math.random().toString(36).slice(2)}`;
const response = await fetch('https://api.flutterwave.com/v3/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FLUTTERWAVE_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
tx_ref,
amount,
currency, // 'NGN', 'KES', 'GHS', 'ZAR', 'USD', etc.
redirect_url: `${process.env.APP_URL}/payment/verify`,
customer: { email, name },
customizations: { title: 'Your Product Name' },
}),
});
const data = await response.json();
if (data.status !== 'success') throw new Error('Flutterwave payment creation failed');
// Redirect user to data.data.link
return { link: data.data.link, tx_ref };
}3. Verify on return (required)
Flutterwave appends transaction_id and tx_ref to your redirect URL. Verify the transaction_id before fulfilling — never trust the redirect alone:
async function verifyFlutterwavePayment(transactionId) {
const response = await fetch(
`https://api.flutterwave.com/v3/transactions/${transactionId}/verify`,
{
headers: {
Authorization: `Bearer ${process.env.FLUTTERWAVE_SECRET_KEY}`,
},
}
);
const data = await response.json();
if (data.data?.status === 'successful') {
return { success: true, data: data.data };
}
return { success: false };
}4. Webhooks for async payment methods
Mobile money and bank transfers don't complete instantly. Webhooks tell your server when money actually arrives. Flutterwave uses a simple hash comparison — no HMAC, just a secret string you set in the dashboard:
// Register your webhook URL in FLW dashboard > Settings > Webhooks
async function handleFlutterwaveWebhook(body, signatureHeader) {
const secretHash = process.env.FLW_WEBHOOK_HASH;
if (signatureHeader !== secretHash) {
throw new Error('Invalid Flutterwave webhook signature');
}
const event = typeof body === 'string' ? JSON.parse(body) : body;
if (
event.event === 'charge.completed' &&
event.data.status === 'successful'
) {
const { tx_ref, amount, currency, customer } = event.data;
// Use tx_ref as idempotency key — this webhook may fire more than once
}
return { received: true };
}5. Flutterwave inline checkout (client-side)
To trigger the checkout modal directly on your page without a redirect:
<script src="https://checkout.flutterwave.com/v3.js"></script>
<button onclick="payWithFlutterwave()">Pay Now</button>
<script>
function payWithFlutterwave() {
FlutterwaveCheckout({
public_key: 'FLWPUBK_TEST-...', // Public key only
tx_ref: 'order-' + Date.now(),
amount: 5000,
currency: 'NGN',
customer: {
email: 'customer@email.com',
name: 'Customer Name',
},
callback: function(data) {
// Always verify data.transaction_id server-side before fulfilling
verifyOnServer(data.transaction_id);
},
onclose: function() {},
});
}
</script>Key currencies and what they unlock
Currency selection determines which local payment methods Flutterwave displays to your user:
- NGN — Nigerian cards, bank transfer, USSD, Opay, PalmPay
- KES — M-Pesa (Kenya's dominant payment method — essential if you have Kenyan users)
- GHS — MTN Mobile Money, Vodafone Cash, AirtelTigo
- ZAR — South African Visa/Mastercard, EFT
- USD / GBP / EUR — international cards, useful for diaspora payments or global users
Common questions
Selling to users across Africa?
We've built Flutterwave integrations for products collecting payments in Nigeria, Kenya, Ghana, and beyond — including setups that route between Flutterwave and Stripe based on where the user is. Tell us your stack and your markets.
Continue reading