Integrate Wave
Wave's Checkout flow, the canonical webhook verification and the production checklist.
Wave Business offers a payment API called Checkout. This guide summarises how it works, then lists what you need before production. Provider sheet: Wave in the directory.
The Wave Checkout flow
Sources: Wave Checkout API and Wave Webhooks, checked on 1 October 2026.
-
Your server creates a session:
POST https://api.wave.com/v1/checkout/sessionswith theAuthorization: Bearer <Wave API key>header. Without it, Wave answers 401. Required fields:amount(string),currency(XOF),success_urlanderror_url(HTTPS). Useful field:client_reference, your order ID. -
Wave returns an
id(prefixcos-) and awave_launch_url. Redirect the customer to that address. -
The customer pays in Wave. The session has two statuses:
checkout_status(open,complete,expired) andpayment_status(processing,cancelled,succeeded). -
Wave sends a webhook:
checkout.session.completedwhen the payment succeeds,checkout.session.payment_failedwhen it fails. Body:{ "id": "EV_…", "type": "…", "data": { …session… } }. -
A session expires 30 minutes after creation. Refunds go through
POST /v1/checkout/sessions/:id/refund(200 response, empty body).
The Checkout documentation we read describes no test environment. To test before production, ask Wave Business for access to its test environment.
Verify webhooks
This function is the canonical version, which the Orange Money and PayTech guides link to.
- The header is called
Wave-Signatureand looks liket=…,v1=…. - The signed message is the timestamp followed directly by the raw body, with
no separator:
HMAC-SHA256(<t><raw body>, secret), in hexadecimal. - During a key rotation the header may contain several
v1values. Accept the message if any of them matches. - Use the raw body, not re-serialised JSON: one extra space changes the signature.
- Reject a message older than 5 minutes. Deduplicate on the event's
idfield. - A missing or malformed header is refused (400 response); it must not crash.
- Another mode offered by Wave: a shared secret sent as
Authorization: Bearer. Wave rates it as riskier.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWaveSignature(header, rawBody, secret, toleranceS = 300) {
if (typeof header !== 'string') return false;
const pairs = header.split(',').map((p) => p.trim().split('='));
const t = Number(pairs.find(([k]) => k === 't')?.[1]);
const signatures = pairs.filter(([k, v]) => k === 'v1' && v).map(([, v]) => v);
if (!Number.isFinite(t) || !signatures.length || Math.abs(Date.now() / 1000 - t) > toleranceS) return false;
const expected = createHmac('sha256', secret).update(`${t}${rawBody}`).digest('hex');
return signatures.some((s) => s.length === expected.length && timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
}Keep a single function that translates Wave statuses into your own states.
Production checklist
- Wave Business account opened. Only administrators see the Developers section of the Business Portal (directory sheet).
- Registered company: RCCM and NINEA (procedure).
- CDP declaration made and receipt received before processing personal data (procedure).
- Your service's role regarding electronic money clarified (procedure).
- Commercial terms and contract: not published in the documentation we read. Discuss them with Wave Business.
- Real Wave base URL and key.
success_url,error_urland webhook URL over HTTPS.- Webhook secret stored outside the code, signature checked on every call.
- Idempotent processing: the same event received twice delivers a single order.
- Daily reconciliation, for example with the Balance & Reconciliation API.