Le verdict en trois phrases
Un webhook de paiement n'est pas un appel unique et garanti : il est rejoué 3 à 10 fois sur 24 à 72 heures, dans le désordre, parfois après un timeout de votre serveur. Sans clé d'idempotence, vérification de signature HMAC et protection contre le rejeu, vous encaissez des doubles débits et perdez des commandes. La règle d'or : vérifier la signature, dédupliquer par event id, répondre 200 vite, traiter en asynchrone, et réconcilier avec l'API de statut comme source de vérité.
Pourquoi les webhooks arrivent en double
Les fournisseurs considèrent un webhook comme livré uniquement si vous répondez 200 rapidement. Le moindre délai déclenche un rejeu. Voici les politiques typiques 2026 (ordre de grandeur, à vérifier dans la doc de chaque fournisseur).
| Fournisseur | Nb de tentatives | Fenêtre de rejeu | Timeout attendu | Signature |
|---|---|---|---|---|
| Paystack | jusqu'à ~5 | ~72 h | < 10 s | x-paystack-signature (HMAC SHA512) |
| Wave | 3 à 10 | 24-48 h | < 10 s | token / signature |
| CinetPay | 3 à 8 | ~48 h | < 15 s | token de vérification |
| Flutterwave | jusqu'à ~5 | ~24 h | < 10 s | vérif-hash header |
| Stripe | jusqu'à ~15 sur 72 h | 72 h | < 10 s | Stripe-Signature |
Conclusion : concevez TOUJOURS votre endpoint comme s'il allait recevoir chaque événement plusieurs fois. L'idempotence n'est pas optionnelle.
Les 5 bugs les plus fréquents
| Bug | Conséquence | Correctif |
|---|---|---|
| Pas de vérification de signature | Faux webhook -> fausse commande payée | Vérifier HMAC avant tout traitement |
| Traitement synchrone lourd | Timeout -> rejeu -> double débit | Répondre 200 vite, traiter en file async |
| Pas de déduplication | Même event traite 2 fois | Stocker event id, ignorer si déjà vu |
| Confiance au montant du payload | Montant falsifié accepté | Réconcilier via l'API de statut |
| Retour 500 sur erreur métier | Rejeu infini du fournisseur | Retourner 200 + logguer pour rejouer soi-même |
Ces cinq erreurs représentent la quasi-totalité des incidents de paiement que nous auditons chez Kolonell.
Le patron robuste en 6 étapes
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.
- Vérifier la signature (HMAC) avec le secret du fournisseur, avant de lire quoi que ce soit d'autre. Signature invalide -> 401.
- Extraire l'event id et vérifier en base s'il a déjà été traité. Déjà vu -> répondre 200 et sortir.
- Enregistrer l'événement brut (event id, payload, date) dans une table de journal.
- Répondre 200 immédiatement, avant tout traitement métier lourd.
- Traiter en asynchrone (file d'attente) : mise à jour de la commande, envoi de la facture, notification.
- Réconcilier avec l'API
getStatusdu fournisseur comme source de vérité avant de marquer "payé".
Mini cas pratique
Ibrahim vend des billets d'événement en ligne à Abidjan. Un vendredi soir, son serveur rame sous la charge : le webhook Wave met 12 secondes à répondre, le timeout est à 10 s. Wave rejoue l'événement 4 fois. Sans idempotence, Ibrahim crée 4 billets pour un seul paiement de 15 000 FCFA et débité... non, en réalité le client n'est débité qu'une fois, mais Ibrahim envoie 4 billets valides, dont 3 seront utilisés frauduleusement : perte de 45 000 FCFA sur une seule commande. Après correction (déduplication par event id + réponse 200 immédiate + traitement async), le même scénario ne crée qu'un billet, quel que soit le nombre de rejeux. Sur un week-end à 400 billets, l'incident aurait coûte plusieurs centaines de milliers de FCFA.
FAQ
Pourquoi vérifier la signature si l'URL du webhook est secrète ? Parce qu'une URL n'est jamais vraiment secrète : elle fuite dans les logs, les proxies, les captures réseau. La signature HMAC prouve que le message vient bien du fournisseur et n'a pas été falsifié. Sans elle, n'importe qui connaissant l'URL peut simuler un paiement.
Que veut dire idempotence concrètement ? Cela signifie que traiter le même événement 1 fois ou 10 fois produit exactement le même résultat. En pratique : stocker l'event id du fournisseur et ignorer tout événement déjà enregistré. Une commande passe à "payée" une seule fois, même après 10 rejeux.
Faut-il faire confiance au montant présent dans le webhook ? Non. Le payload peut être incomplet ou, en cas de faille, falsifié. Réconciliez toujours le montant et le statut via l'API de statut du fournisseur avant de livrer la commande.
Pourquoi répondre 200 avant de traiter ? Parce que le fournisseur attend un accusé rapide (souvent < 10 s). Si votre traitement métier est lent, vous dépassez le timeout et déclenchez des rejeux. Accusez réception d'abord, traitez ensuite en file asynchrone.
Comment tester tout ça sans vrais paiements ? Utilisez le sandbox du fournisseur et sa fonction de rejeu d'événements. Envoyez deux fois le même event pour vérifier la déduplication, envoyez une signature invalide pour vérifier le rejet 401. Kolonell livre chaque intégration avec cette batterie de tests.
Discutons de votre projet. On audite vos webhooks ou on livre une intégration paiement idempotente et signée, testée en sandbox. WhatsApp +221 77 596 93 33.
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.

