The problem with manual payment verification
Most small Senegalese e-commerce stores check Wave payments by opening the app and scanning the transaction list. That works for five orders a day. It breaks completely at fifty. Wave Business webhooks let your server receive an automatic HTTP notification the moment a payment completes, fails, or expires — no polling, no manual checks, no missed orders.
How Wave webhooks work
When a payment session changes status, Wave sends an HTTP POST to a URL you register in the Wave Business dashboard. The payload is a JSON object describing the event type and the associated payment data.
Sample Wave webhook payload:
`json
{
"id": "evt_01HX9K2M",
"type": "checkout.completed",
"data": {
"id": "chk_01HX9K1A",
"client_reference": "order_1042",
"amount": "50000",
"currency": "XOF",
"when_completed": "2026-06-09T10:15:44Z"
}
}
`
The client_reference field is the value you passed when creating the checkout session — it's the bridge between the Wave event and your database order.
Validating the HMAC-SHA256 signature
Every incoming webhook must be authenticated before processing. Wave signs each request with a Wave-Signature header containing an HMAC-SHA256 of the raw request body using your webhook secret.
Validation in Node.js / TypeScript:
`typescript
import crypto from 'crypto';
function validateWaveWebhook(
rawBody: Buffer,
signatureHeader: string,
secret: string
): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
Need a professional website?
Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.
`
Use timingSafeEqual to prevent timing attacks. Reject any request where the signature does not match with HTTP 400 — do not process it further.
Idempotency: handling duplicate deliveries
Wave may deliver the same event more than once if your server timed out on a previous attempt. Without idempotency guards, an order could be fulfilled twice. The fix is a processed_events table with a unique constraint on the event ID:
`sql
CREATE TABLE processed_events (
event_id VARCHAR(64) PRIMARY KEY,
processed_at TIMESTAMPTZ DEFAULT NOW()
);
`
Before processing:
`typescript
const result = await db.query(
'INSERT INTO processed_events(event_id) VALUES($1) ON CONFLICT DO NOTHING RETURNING event_id',
[event.id]
);
if (result.rowCount === 0) {
return res.status(200).json({ status: 'already_processed' });
}
`
Handling timeouts and retry logic
Wave marks a webhook as failed if your server does not respond with HTTP 200 within 10 seconds. It then retries with exponential backoff: after 1 minute, 5 minutes, 30 minutes, 2 hours, then stops.
Best practices:
- Respond HTTP 200 immediately, before finishing processing. Offload the actual work to a queue (BullMQ, a database-backed job table, or even
setImmediatefor small volumes). - Monitor your webhook endpoint with Uptime Robot or Better Uptime. A 2-hour outage means you'll miss Wave's final retry attempt.
- Log every incoming webhook to a
webhook_logtable before any processing — this gives you a full audit trail and a source for manual replay.
Building the reconciliation job
Even with perfectly configured webhooks, edge cases exist. Build a reconciliation job that runs every 15–30 minutes:
- Fetch all orders in "pending" status that are more than 10 minutes old.
- For each, call
GET /v1/checkout/sessions/{session_id}on the Wave API. - If the API returns
status: "complete", mark the order as paid and trigger fulfillment. - If
status: "expired", cancel the order and release reserved inventory.
For a store processing 500,000–2,000,000 FCFA per day, this safety net is non-negotiable. Webhooks cover 99% of cases; the reconciliation job covers the other 1%.
FAQ
How long does Wave retry failed webhooks?
Wave retries for approximately 24 hours with exponential backoff. After that, no further retries occur — which is why the polling reconciliation job is essential.
Can I have multiple webhook endpoints for the same API key?
Wave Business supports one endpoint per API key. Use separate API keys (and separate endpoints) for staging and production environments.
Is HTTPS required for receiving Wave webhooks?
Yes. Wave will not deliver webhooks to plain HTTP URLs. Your endpoint must be reachable over HTTPS with a valid certificate — Let's Encrypt works fine.
How do I test webhooks locally before deploying?
Use ngrok or a similar tunnel tool to expose your local server with a public HTTPS URL. Register that temporary URL in the Wave Business dashboard for testing, then switch to your production URL at deploy time.
Let's talk about your project. Kolonell builds Wave-integrated checkout systems with full webhook handling and automated reconciliation. WhatsApp +221 77 596 93 33.
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.
