PayDunya handles ~38% of online payments in Francophone Africa as of late 2025. It's the reference aggregator when you want a single provider for Wave + Orange Money + card. But the official Node SDK has been stale since 2023, REST docs are scattered, and 80% of production integrations we audit have IPN bugs.
TL;DR
- Official Node SDK stale → use community
paydunya-sdkor direct REST.- 3 modes:
direct-pay(wallet only),checkout(hosted page),onsite(advanced, strict KYC).- HMAC-signed IPN webhook, idempotency mandatory.
- Settlement T+1 business day to UEMOA bank account or IBAN.
Target architecture
`
[Site] → init invoice → [PayDunya API]
↓
[PayDunya hosted page]
↓
[Customer pays: Wave / OM / card]
↓
redirect IPN webhook
↓ ↓
[Return page] [POST /api/webhooks/paydunya]
↓
[Verify signature]
↓
[Mark order "paid"]
`
Step 1 — create developer account
paydunya.com→ Sign up → "Developers"- TEST mode by default (
test-...keys) - Switch to LIVE: full KYC (ID, RCCM, NINEA, IBAN) → 5-10 business day validation
Keys to retrieve:
MASTER_KEY(account ID)PRIVATE_KEY(signature)PUBLIC_KEY(frontend if onsite mode)TOKEN(API auth)
Step 2 — invoice (checkout mode)
`ts
const PD_BASE = 'https://app.paydunya.com/api/v1';
interface CreateInvoiceInput {
orderId: string;
totalXof: number;
items: { name: string; qty: number; unitPriceXof: number }[];
customerEmail?: string;
customerName?: string;
customerPhone?: string;
}
export async function createInvoice(input: CreateInvoiceInput) {
const res = await fetch(${PD_BASE}/checkout-invoice/create, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'PAYDUNYA-MASTER-KEY': process.env.PD_MASTER_KEY!,
'PAYDUNYA-PRIVATE-KEY': process.env.PD_PRIVATE_KEY!,
'PAYDUNYA-TOKEN': process.env.PD_TOKEN!,
},
body: JSON.stringify({
invoice: {
total_amount: input.totalXof,
description: Order ${input.orderId},
items: Object.fromEntries(
input.items.map((it, i) => [item_${i}, {
name: it.name,
quantity: it.qty,
unit_price: String(it.unitPriceXof),
total_price: String(it.unitPriceXof * it.qty),
}])
),
},
store: { name: 'Kolonell Boutique' },
custom_data: { order_id: input.orderId },
actions: {
callback_url: 'https://kolonell.com/api/webhooks/paydunya',
return_url: https://kolonell.com/checkout/return?order=${input.orderId},
cancel_url: https://kolonell.com/checkout/cancel?order=${input.orderId},
},
}),
});
const data = await res.json();
if (data.response_code !== '00') throw new Error(PayDunya error: ${data.response_text});
return { token: data.token, redirectUrl: data.response_text };
}
`
Step 3 — IPN webhook
`ts
Need a professional website?
Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.
import crypto from 'crypto';
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function POST(req: NextRequest) {
const formData = await req.formData();
const data = Object.fromEntries(formData.entries()) as Record
const expectedHash = crypto
.createHash('sha512')
.update(process.env.PD_MASTER_KEY! + data.invoice_token)
.digest('hex');
if (data.hash !== expectedHash) {
return NextResponse.json({ error: 'invalid_signature' }, { status: 401 });
}
const existing = await prisma.paymentEvent.findUnique({ where: { externalId: data.invoice_token } });
if (existing) return NextResponse.json({ ok: true, deduped: true });
const verifyRes = await fetch(
https://app.paydunya.com/api/v1/checkout-invoice/confirm/${data.invoice_token},
{ headers: {
'PAYDUNYA-MASTER-KEY': process.env.PD_MASTER_KEY!,
'PAYDUNYA-PRIVATE-KEY': process.env.PD_PRIVATE_KEY!,
'PAYDUNYA-TOKEN': process.env.PD_TOKEN!,
}}
);
const verified = await verifyRes.json();
if (verified.status === 'completed') {
await prisma.$transaction([
prisma.paymentEvent.create({
data: { externalId: data.invoice_token, provider: 'paydunya', type: 'invoice_completed', payload: verified },
}),
prisma.order.update({
where: { id: verified.custom_data.order_id },
data: { status: 'paid', paidAt: new Date() },
}),
]);
}
return NextResponse.json({ ok: true });
}
`
Step 4 — refunds
No public PayDunya refund API in May 2026. Procedure:
- Request via PayDunya partner dashboard
- Manual PayDunya team validation (24-72h)
- Settlement per original timing
Partial automation: create refund_requests table with status, PayDunya agent validates, IPN callback received as confirmation.
Step 5 — alternative modes
Direct Pay (wallet only)
For ONLY Wave or OM, no PayDunya checkout page:
`ts
const res = await fetch('https://app.paydunya.com/api/v1/direct-pay/credit-account', {
method: 'POST',
headers: { /* ... keys ... */ },
body: JSON.stringify({ account_alias: '+221771234567', amount: '5000' }),
});
`
Onsite mode
Advanced: payment capture INSIDE your UI (no redirect). Requires Tier-2 KYC + PCI DSS audit if card. For 95% of cases, checkout mode is enough.
Common pitfalls
- Test vs live: forgetting to switch keys in prod. Always detect via
process.env.NODE_ENV. unit_priceas string: PayDunya requires string, not number."5000"not5000.custom_data: needed to pass yourorder_idback in IPN.- IPN hash verification: 70% of integrations skip it → endpoint exposed.
- Manual settlement: switch to "Auto settlement" in PayDunya dashboard, otherwise money stays in virtual account.
Fees and settlement
| Method | Fees | Settlement |
|---|---|---|
| Wave Senegal | 1.2-2.0% negotiated | T+1 UEMOA IBAN |
| Wave Ivory Coast | 2.0% | T+1 UEMOA IBAN |
| Orange Money | 1.8-2.3% | T+1 UEMOA IBAN |
| Visa/Mastercard | 3.5% + 100 XOF | T+3 UEMOA IBAN |
| Free Money | 2.5% | T+1 |
Negotiation possible from 2-3M XOF monthly volume — discuss with your PayDunya AM.
Real case — Dakar cosmetics shop (12 months)
| Metric | Value |
|---|---|
| Monthly volume | 14M XOF |
| Average PayDunya fees | 1.7% |
| 12-month fee cost | 2.86M XOF |
| Payment failure rate | 4.2% (vs 11% other aggregator) |
| Bank settlement delay | T+1 (never missed) |
FAQ
Q: Node SDK or direct REST?
A: Direct REST — community SDK isn't maintained. More control, fewer bugs.
Q: Can I test without KYC?
A: Yes, TEST mode with sandbox keys. Payments are simulated. KYC required only to switch to LIVE.
Q: How many currencies?
A: XOF, XAF (CEMAC), GHS, NGN. EUR/USD via Stripe partnership.
Conclusion
PayDunya remains the best gateway for UEMOA-focused e-commerce in 2026. Clean end-to-end integration takes 3-5 dev-days. Immediate ROI: a reliable aggregator easily justifies 1.5-2% fees vs managing 4 separate connectors.
Mohamed Bah
Fondateur, Kolonell
Passionate about digital and entrepreneurship in Africa, Mohamed has been helping Sénégalese businesses with their digital transformation since 2020. Founder of Kolonell, he believes every SME deserves a professional and accessible online présence.
