Sites Web11 min de lecture

Wave Business webhook : intégration site + idempotence en 47 minutes (tutoriel 2026)

Mohamed Bah·Fondateur, Kolonell
5 mai 2026
Partager :
Wave Business webhook : intégration site + idempotence en 47 minutes (tutoriel 2026)

Wave Business webhook : intégration site + idempotence en 47 minutes (tutoriel 2026)

Sites Web

La majorité des PME sénégalaises qui intègrent Wave dans leur site copient un bout de doc, posent un endpoint, et signent leur tranquillité d'esprit. Trois mois plus tard, le premier double-paiement arrive : l'utilisateur a recliqué, le webhook s'est déclenché deux fois, et la commande a été expédiée deux fois. Ce guide pose la version production-ready.

TL;DR

- Le webhook Wave Business n'est pas idempotent par défaut : à vous de gérer le déduplicage côté serveur.

- Vérifiez TOUJOURS la signature HMAC avant d'écrire en base.

- Stockez l'event_id Wave dans une table payment_events avec contrainte unique : seconde réception = no-op.

- Délai de retry Wave : 5 tentatives sur 24h, backoff exponentiel. Renvoyez 200 OK même sur événement déjà traité.

Architecture cible

`

[Wave POS / Wave App] → [Wave Backend] → POST /api/webhooks/wave

  • Vérifier HMAC signature
  • Parse JSON
  • Check event_id en DB → idempotent ?
  • Si nouveau → écrire payment + commit
  • Toujours répondre 200 OK

`

Étape 1 — endpoint Next.js App Router

`ts

// app/api/webhooks/wave/route.ts

import crypto from 'crypto';

import { NextRequest, NextResponse } from 'next/server';

import { prisma } from '@/lib/prisma';

const WAVE_WEBHOOK_SECRET = process.env.WAVE_WEBHOOK_SECRET!;

export async function POST(req: NextRequest) {

const rawBody = await req.text();

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

// 1. Vérification HMAC

const expected = crypto

.createHmac('sha256', WAVE_WEBHOOK_SECRET)

.update(rawBody)

.digest('hex');

if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {

return NextResponse.json({ error: 'invalid_signature' }, { status: 401 });

}

const event = JSON.parse(rawBody);

// 2. Idempotence : check event_id

const existing = await prisma.paymentEvent.findUnique({

where: { externalId: event.id },

});

if (existing) return NextResponse.json({ ok: true, deduped: true });

// 3. Persister atomiquement

await prisma.$transaction([

prisma.paymentEvent.create({

data: {

externalId: event.id,

provider: 'wave',

type: event.type,

payload: event,

},

}),

prisma.order.update({

where: { id: event.metadata.order_id },

data: { status: 'paid', paidAt: new Date() },

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.

}),

]);

return NextResponse.json({ ok: true });

}

`

Étape 2 — table payment_events (Prisma)

`prisma

model PaymentEvent {

id String @id @default(cuid())

externalId String @unique // event.id Wave

provider String // "wave" | "orange-money" | "stripe"

type String // "checkout.session.completed" etc.

payload Json

receivedAt DateTime @default(now())

@@index([provider, type])

}

`

La contrainte @unique sur externalId est votre filet de sécurité : même si la logique applicative laisse passer un doublon, Postgres rejettera l'insert.

Étape 3 — tester sans Wave en sandbox

Wave ne fournit pas d'environnement de test public stable au Sénégal en mai 2026. Deux options :

Option A — fixture locale + curl :

`bash

SECRET="votre_secret_local"

BODY='{"id":"evt_test_123","type":"merchant.payment_received","amount":15000,"currency":"XOF","metadata":{"order_id":"ord_42"}}'

SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -X POST http://localhost:3000/api/webhooks/wave \

-H "Content-Type: application/json" \

-H "wave-signature: $SIG" \

-d "$BODY"

`

Option B — ngrok + numéro de test Wave réel : créez une commande à 100 FCFA, payez, vérifiez la trace en DB.

Erreurs classiques (audit Kolonell sur 23 sites en 2025)

#ErreurConséquenceFix
1Pas de vérification HMACEndpoint exposé, faux paiements possiblesVérifier la signature avant tout
2Idempotence par order_id au lieu de event_idDouble traitement si Wave envoie 2 events sur la même commande (init + completed)Dédupliquer par event_id
3Réponse 500 sur événement déjà traitéWave retry 5x, log spam, alerting casséRetourner 200 OK même sur dedup
4Lecture du body parsé par middlewareSignature invalide (body modifié)Lire req.text() brut avant parse
5Pas de timeout DBWebhook bloque > 10s, Wave timeout, retry inutileIndex DB + transaction courte

FAQ

Q : Combien de temps Wave attend une réponse ?

R : 10 secondes. Au-delà, Wave considère l'événement comme échoué et retry.

Q : Le webhook Wave envoie-t-il des doublons ?

R : Oui, en cas de retry suite à timeout. C'est exactement pourquoi l'idempotence est obligatoire.

Q : Faut-il une queue (Redis/BullMQ) ?

R : Pour < 1000 paiements/jour, un endpoint synchrone avec transaction Postgres suffit. Au-delà, déchargez vers une queue.

Notre intégration type chez Kolonell

Pour un client e-commerce moyen (boutique mode 200 commandes/mois), l'intégration Wave production-ready prend 47 minutes :

  • 12 min : créer les routes API + table payment_events
  • 15 min : implémenter signature HMAC + idempotence
  • 10 min : tester avec fixture + commande réelle
  • 10 min : connecter au workflow commande (email confirmation, MAJ statut)

Voir notre tutoriel Orange Money complémentaire →

Tags :#Wave#Webhook#Paiement#Sénégal#Next.js#Tutoriel
Partager :

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.