Websites11 min read

Wave Business B2B: payroll + API in 2026 (Senegal, CI)

Mohamed Bah·Fondateur, Kolonell
May 19, 2026
Share:
Wave Business B2B: payroll + API in 2026 (Senegal, CI)

Wave Business B2B: payroll + API in 2026 (Senegal, CI)

Websites

Manual payroll in Senegal or Ivory Coast = accounting nightmare: bank transfers one by one, cash for unbanked, recurring delays, employee conflicts. Wave Business has offered since 2024 a B2B API to disburse salaries in bulk via mobile money.

TL;DR

- Wave Business B2B API: bulk disburse payroll to employee wallets.

- Cost: 0.5-1.0% per transaction (negotiated by volume).

- Multi-country UEMOA compatible (SN, CI, ML, BF).

- Compliance: separate CSS / IPRES + IR salary declarations.

Automated payroll workflow

`

[Month M: prepare payroll file]

[HR + accountant validation]

[POST Wave Business API: bulk_disbursement]

[Wave processes + sends SMS confirmation to each employee]

[Per-transaction success/failure webhook]

[Auto accounting reconciliation]

[Pay stubs generated + sent WhatsApp/email]

`

Step 1 — data model

`prisma

model Employee {

id String @id @default(cuid())

organizationId String

firstName String

lastName String

email String

phone String

whatsapp String?

ninea String?

cniNumber String @unique

iban String?

walletProvider String

walletNumber String

baseSalary Int

contractType String

startedAt DateTime

endedAt DateTime?

isActive Boolean

}

model PayrollRun {

id String @id @default(cuid())

organizationId String

month String

status String

totalGross Int

totalNet Int

totalTaxes Int

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

cssContribution Int

incomeTax Int

netSalary Int

paymentStatus String

walletTransactionId String?

payslipPdfUrl String?

}

`

Step 2 — Senegal net salary computation

In Senegal, 2026 legal deductions:

  • IPRES: 5.6% employee (employer 8.4%)
  • CSS: 7% employee on capped amount
  • Income tax (IR): 0-40% brackets

`ts

export function computeNetSenegal(grossXof: number) {

const ipresEmployee = grossXof * 0.056;

const cssEmployee = Math.min(grossXof, 432000) * 0.07;

const taxableIncome = grossXof - ipresEmployee - cssEmployee;

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),

};

}

`

Step 3 — bulk disbursement via Wave API

`ts

async function disbursePayroll(payrollRunId: string) {

const run = await prisma.payrollRun.findUnique({

where: { id: payrollRunId },

include: { items: { include: { employee: true } } },

});

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,

Need a professional website?

Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.

narration: Salary ${run.month},

})),

};

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();

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 prevents double disbursement on bugs.

Step 4 — confirmation webhooks

`ts

export async function POST(req: NextRequest) {

const body = await req.text();

const signature = req.headers.get('wave-signature') ?? '';

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' },

});

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 });

}

`

Step 5 — automatic pay stubs

`ts

import PDFKit from 'pdfkit';

async function generatePayslip(payrollItem) {

const doc = new PDFKit();

const buffers = [];

doc.on('data', buffers.push.bind(buffers));

doc.fontSize(16).text(PAY STUB - ${payrollItem.payrollRun.month});

doc.fontSize(10).text(Employer: ${org.name} (NINEA ${org.ninea}));

doc.text(Employee: ${employee.firstName} ${employee.lastName});

doc.text(ID: ${employee.id});

doc.text(CNI: ${employee.cniNumber});

doc.moveDown();

doc.text('EARNINGS', { underline: true });

doc.text(Base salary: ${item.baseSalary.toLocaleString()} XOF);

doc.text(Bonuses: ${item.bonuses.toLocaleString()} XOF);

doc.text(Overtime: ${item.overtime.toLocaleString()} XOF);

doc.text(GROSS: ${item.grossSalary.toLocaleString()} XOF);

doc.text('DEDUCTIONS', { 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 TO PAY: ${item.netSalary.toLocaleString()} XOF, { align: 'right' });

doc.fontSize(9).text(Sent to Wave ${employee.walletNumber} on ${new Date().toLocaleDateString('en-SN')});

doc.end();

const pdfBuffer = Buffer.concat(buffers);

const url = await uploadToSpaces(payslips/${item.id}.pdf, pdfBuffer);

await prisma.payrollItem.update({

where: { id: item.id },

data: { payslipPdfUrl: url },

});

await sendWhatsApp(employee.whatsapp, {

template: 'payslip_available',

params: [employee.firstName, payrollItem.payrollRun.month, url],

});

}

`

Real case — Dakar SME 47 employees

MetricBeforeAfter Wave Business B2B
Payroll prep time/month12h1h
Amount errors8%<1%
Salary received delay3-5d (transfers)<5 min (Wave)
Monthly processing cost65K XOF (bank fees)22K XOF (Wave fees)
Employee complaints/month111

ROI: 11h HR/month savings + 43K XOF/month = ~600K XOF/year equivalent.

Common pitfalls

  • Employees without Wave — solution: pay via OM through PayDunya OR bank transfer for banked.
  • Employee data exposed — wallet numbers = sensitive. Mandatory encryption + audit logs.
  • No 4-eyes validation — PayrollRun >5M XOF must be validated by 2 people (RBAC).
  • Accounting reconciliation — need monthly SYSCOHADA accounting export.
  • Tax compliance — mandatory monthly IPRES/CSS/IR declarations (DGI eDGI).

FAQ

Q: Wave Business available outside SN/CI?

A: Yes, expanding to ML, BF, OG. Each country has separate account.

Q: Competitors?

A: Orange Money Business (similar), MTN MoMo Bulk Payments (Anglophone Africa). Wave remains cheapest (1% vs 2-3%).

Q: Bank payroll vs Wave?

A: Bank = safe, Wave = fast. For <50 employees, mix Wave (60%) + bank (40% premium executives) ideal.

Conclusion

Wave Business B2B API is the most underused B2B tool in Francophone Africa in 2026. Considerable operational savings for SMEs. 1.5-4M XOF integration investment. 4-8 month ROI. 2027 standard for any SME >30 employees.

Tags:#Wave Business#B2B#Payroll#Salaries#API#Africa
Share:

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.