La paie manuelle au Sénégal ou Côte d'Ivoire = nightmare comptabilité : virements bancaires un par un, espèces pour les non-bancarisés, retards récurrents, conflits employés. Wave Business propose depuis 2024 une API B2B pour disburser des salaires en masse via mobile money.
TL;DR
- Wave Business B2B API : disburse paie en bulk vers wallets employés.
- Coût : 0.5-1.0 % par transaction (négocié à volume).
- Compatible multi-pays UEMOA (SN, CI, ML, BF).
- Conformité : déclaration CSS / IPRES + IR salaires séparée.
Workflow paie automatisée
`
[Mois M : préparer fichier paie]
↓
[Validation DRH + comptable]
↓
[POST API Wave Business : bulk_disbursement]
↓
[Wave traite + envoie SMS confirmation à chaque employé]
↓
[Webhook réussite/échec par transaction]
↓
[Reconciliation comptable auto]
↓
[Bulletins paie générés + envoyés WhatsApp/email]
`
Étape 1 — modèle de données
`prisma
model Employee {
id String @id @default(cuid())
organizationId String
firstName String
lastName String
email String
phone String // E.164
whatsapp String?
ninea String?
cniNumber String @unique
iban String? // pour virement bancaire si applicable
walletProvider String // WAVE / ORANGE_MONEY / FREE / BANK
walletNumber String // n° wallet ou IBAN selon provider
baseSalary Int // brut XOF
contractType String // CDI / CDD / INTERIM
startedAt DateTime
endedAt DateTime?
isActive Boolean
}
model PayrollRun {
id String @id @default(cuid())
organizationId String
month String // "2026-05"
status String // DRAFT / VALIDATED / DISBURSING / COMPLETED / FAILED
totalGross Int
totalNet Int
totalTaxes Int // CSS + IPRES + IR
validatedBy String?
validatedAt DateTime?
disbursedAt DateTime?
items PayrollItem[]
}
model PayrollItem {
id String @id @default(cuid())
payrollRunId String
payrollRun PayrollRun @relation(fields: [payrollRunId], references: [id])
employeeId String
employee Employee @relation(fields: [employeeId], references: [id])
baseSalary Int
bonuses Int
overtime Int
grossSalary Int
ipresContribution Int // 5.6 % salarié + 8.4 % employeur SN
cssContribution Int // 7 %
incomeTax Int // tranches IR
netSalary Int
paymentStatus String // PENDING / SENT / RECEIVED / FAILED
walletTransactionId String?
payslipPdfUrl String?
}
`
Étape 2 — calcul net salaires Sénégal
Au Sénégal, retenues légales 2026 :
- IPRES : 5.6 % salarié (employeur 8.4 %)
- CSS : 7 % salarié sur plafond
- Impôt sur le revenu (IR) : tranches 0-40 %
`ts
// lib/payroll/senegal.ts
export function computeNetSenegal(grossXof: number) {
const ipresEmployee = grossXof * 0.056;
const cssEmployee = Math.min(grossXof, 432000) * 0.07; // plafond CSS
const taxableIncome = grossXof - ipresEmployee - cssEmployee;
// Barème IR Sénégal 2026 (annuel converti mensuel)
let incomeTax = 0;
if (taxableIncome > 50000) {
if (taxableIncome <= 75000) incomeTax = (taxableIncome - 50000) * 0.20;
else if (taxableIncome <= 100000) incomeTax = 5000 + (taxableIncome - 75000) * 0.25;
else if (taxableIncome <= 200000) incomeTax = 11250 + (taxableIncome - 100000) * 0.30;
else if (taxableIncome <= 500000) incomeTax = 41250 + (taxableIncome - 200000) * 0.35;
else incomeTax = 146250 + (taxableIncome - 500000) * 0.40;
}
const netSalary = grossXof - ipresEmployee - cssEmployee - incomeTax;
return {
gross: grossXof,
ipres: Math.round(ipresEmployee),
css: Math.round(cssEmployee),
incomeTax: Math.round(incomeTax),
net: Math.round(netSalary),
};
}
`
Étape 3 — bulk disbursement via API Wave
`ts
// jobs/disburse-payroll.ts
async function disbursePayroll(payrollRunId: string) {
const run = await prisma.payrollRun.findUnique({
where: { id: payrollRunId },
include: { items: { include: { employee: true } } },
});
// Filtrer employés Wave uniquement
const waveItems = run.items.filter(i => i.employee.walletProvider === 'WAVE');
const bulkRequest = {
receivers: waveItems.map(item => ({
external_id: item.id,
amount: String(item.netSalary),
currency: 'XOF',
receiver_msisdn: item.employee.walletNumber,
narration: Salaire ${run.month},
})),
};
Besoin d'un site web professionnel ?
Kolonell crée des sites web qui attirent des clients, optimisés pour le marché sénégalais. Devis gratuit en 2 minutes.
const res = await fetch('https://api.wave.com/v1/business/bulk-disbursements', {
method: 'POST',
headers: {
'Authorization': Bearer ${process.env.WAVE_BUSINESS_API_KEY},
'Content-Type': 'application/json',
'Idempotency-Key': payroll_${payrollRunId},
},
body: JSON.stringify(bulkRequest),
});
const result = await res.json();
// Wave retourne batch_id + statuts individuels par receiver
// Mettre à jour PayrollItems
for (const r of result.results) {
await prisma.payrollItem.update({
where: { id: r.external_id },
data: {
paymentStatus: r.status === 'success' ? 'SENT' : 'FAILED',
walletTransactionId: r.transaction_id,
},
});
}
}
`
Idempotency-Key empêche double disbursement si bug.
Étape 4 — webhooks confirmation
`ts
// app/api/webhooks/wave-business/route.ts
export async function POST(req: NextRequest) {
const body = await req.text();
const signature = req.headers.get('wave-signature') ?? '';
// Vérifier signature HMAC
const expected = crypto
.createHmac('sha256', process.env.WAVE_BUSINESS_WEBHOOK_SECRET!)
.update(body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return NextResponse.json({ error: 'invalid_signature' }, { status: 401 });
}
const event = JSON.parse(body);
if (event.type === 'disbursement.completed') {
await prisma.payrollItem.update({
where: { id: event.external_id },
data: { paymentStatus: 'RECEIVED' },
});
// Notifier employé WhatsApp
const item = await prisma.payrollItem.findUnique({
where: { id: event.external_id },
include: { employee: true, payrollRun: true },
});
await sendWhatsApp(item.employee.whatsapp ?? item.employee.phone, {
template: 'payroll_received',
params: [
item.employee.firstName,
item.netSalary.toLocaleString(),
item.payrollRun.month,
],
});
}
return NextResponse.json({ ok: true });
}
`
Étape 5 — bulletins paie automatiques
`ts
// jobs/generate-payslips.ts
import PDFKit from 'pdfkit';
async function generatePayslip(payrollItem) {
const doc = new PDFKit();
const buffers = [];
doc.on('data', buffers.push.bind(buffers));
// En-tête
doc.fontSize(16).text(BULLETIN DE PAIE - ${payrollItem.payrollRun.month});
doc.fontSize(10).text(Employeur : ${org.name} (NINEA ${org.ninea}));
doc.text(Employé : ${employee.firstName} ${employee.lastName});
doc.text(Matricule : ${employee.id});
doc.text(CNI : ${employee.cniNumber});
// Détail
doc.moveDown();
doc.text('GAINS', { underline: true });
doc.text(Salaire de base : ${item.baseSalary.toLocaleString()} XOF);
doc.text(Primes : ${item.bonuses.toLocaleString()} XOF);
doc.text(Heures supp. : ${item.overtime.toLocaleString()} XOF);
doc.text(SALAIRE BRUT : ${item.grossSalary.toLocaleString()} XOF);
doc.text('RETENUES', { underline: true });
doc.text(IPRES (5.6%) : -${item.ipresContribution.toLocaleString()} XOF);
doc.text(CSS (7%) : -${item.cssContribution.toLocaleString()} XOF);
doc.text(IR : -${item.incomeTax.toLocaleString()} XOF);
doc.fontSize(14).text(NET À PAYER : ${item.netSalary.toLocaleString()} XOF, { align: 'right' });
doc.fontSize(9).text(Versé sur Wave ${employee.walletNumber} le ${new Date().toLocaleDateString('fr-SN')});
doc.end();
const pdfBuffer = Buffer.concat(buffers);
// Upload to S3
const url = await uploadToSpaces(payslips/${item.id}.pdf, pdfBuffer);
// Update DB
await prisma.payrollItem.update({
where: { id: item.id },
data: { payslipPdfUrl: url },
});
// Send WhatsApp
await sendWhatsApp(employee.whatsapp, {
template: 'payslip_available',
params: [employee.firstName, payrollItem.payrollRun.month, url],
});
}
`
Cas réel — PME Dakar 47 employés
| Métrique | Avant | Après Wave Business B2B |
|---|---|---|
| Temps préparation paie/mois | 12h | 1h |
| Erreurs montants | 8 % | < 1 % |
| Délai salaires reçus | 3-5j (virements) | < 5 min (Wave) |
| Coût processing/mois | 65K XOF (frais bancaires) | 22K XOF (frais Wave) |
| Plaintes employés/mois | 11 | 1 |
ROI : économie 11h DRH/mois + 43K FCFA/mois = ~600K FCFA/an équivalent.
Pièges fréquents
- Employés sans Wave — solution : reverser sur OM via PayDunya OU virement bancaire pour bancarisés.
- Données employés exposées — wallet numbers = sensibles. Encryption + audit logs obligatoires.
- Pas de validation 4-yeux — un PayrollRun > 5M FCFA doit être validé par 2 personnes (RBAC).
- Reconciliation comptable — besoin export comptable mensuel SYSCOHADA.
- Conformité fiscale — déclarations IPRES/CSS/IR mensuelles obligatoires (DGI eDGI).
FAQ
Q : Wave Business disponible ailleurs qu'au SN/CI ?
R : Oui, en cours d'expansion vers ML, BF, OG. Chaque pays a un compte séparé.
Q : Concurrents ?
R : Orange Money Business (similaire), MTN MoMo Bulk Payments (Anglophone Africa). Wave reste le moins cher (1 % vs 2-3 %).
Q : Paie bancaire vs Wave ?
R : Bancaire = sûre, Wave = rapide. Pour < 50 employés, mix Wave (60 %) + bancaire (40 % cadres premium) idéal.
Conclusion
Wave Business B2B API est l'outil B2B le plus sous-exploité d'Afrique francophone en 2026. Économies opérationnelles considérables pour PME. Investissement intégration 1.5-4M FCFA. ROI 4-8 mois. Standard 2027 pour toute PME > 30 employés.
Mohamed Bah
Fondateur, Kolonell
Passionné par le digital et l'entrepreneuriat en Afrique, Mohamed accompagne les entreprises sénégalaises dans leur transformation digitale depuis 2020. Fondateur de Kolonell, il croit que chaque PME mérite une présence en ligne professionnelle et accessible.
