Pourquoi le webhook est le maillon critique
Un webhook de paiement est l'appel serveur à serveur que le prestataire (Orange Money, Wave, Stripe) envoie à votre backend quand un paiement change de statut. C'est lui, et non la redirection du navigateur, qui fait foi. Un webhook mal sécurisé ouvre la porte aux fausses confirmations de paiement et aux doubles traitements.
Ce guide couvre les quatre piliers d'un webhook robuste : vérification de signature, idempotence, réponse HTTP correcte, et gestion des retries.
Anatomie d'un payload de webhook
Un webhook arrive par une requête POST vers votre endpoint, avec un corps JSON et des en-têtes de signature.
| Champ | Type | Description |
|---|---|---|
| event_id | chaîne | Identifiant unique de l'événement, clé d'idempotence |
| event_type | chaîne | Type, par exemple payment.succeeded |
| order_id | chaîne | Référence de la commande côté marchand |
| amount | entier | Montant en FCFA |
| status | chaîne | SUCCESS, FAILED, PENDING |
| created_at | timestamp | Horodatage de l'événement |
| signature | chaîne | HMAC du corps, dans l'en-tête |
En-têtes à lire
La signature voyage dans un en-tête dédié, souvent accompagné d'un horodatage pour bloquer les attaques par rejeu.
| En-tête | Rôle |
|---|---|
| X-Signature | HMAC SHA-256 du corps brut |
| X-Timestamp | Horodatage d'envoi, pour fenêtre anti-rejeu |
| Content-Type | application/json |
Vérification de signature HMAC
La signature prouve que le webhook vient bien du prestataire et que le corps n'a pas été modifié. Le calcul se fait sur le corps brut, jamais sur le JSON re-sérialisé.
- Récupérer le corps brut de la requête, octet pour octet.
- Récupérer l'en-tête X-Signature et X-Timestamp.
- Calculer HMAC SHA-256 du corps avec votre secret de webhook.
- Comparer en temps constant la signature calculée à celle reçue.
- Rejeter si l'horodatage est plus vieux que 5 minutes (anti-rejeu).
Erreur classique
Beaucoup de bugs viennent d'un framework qui parse le JSON avant que vous lisiez le corps brut. Re-sérialiser change les espaces et casse le HMAC. Lisez toujours le raw body avant tout parsing.
Idempotence : ne traiter qu'une seule fois
Les prestataires renvoient le même webhook plusieurs fois (retries). Votre logique doit être idempotente : traiter event_id deux fois ne doit jamais débiter deux fois ni livrer deux fois.
| Stratégie | Mécanisme | Avantage |
|---|---|---|
| Clé unique en base | Index unique sur event_id | Rejet immédiat des doublons |
| Verrou par commande | Lock sur order_id pendant traitement | Évite les conditions de course |
| Statut transactionnel | Transition PENDING vers PAID atomique | Pas de double passage |
Le plus simple et robuste : un index unique sur event_id. Si l'insertion échoue pour doublon, renvoyez 200 sans retraiter.
Réponse HTTP et codes de statut
Ce que vous renvoyez décide si le prestataire réessaie ou non. Une mauvaise réponse provoque des retries inutiles ou, pire, la perte de l'événement.
| Code renvoyé | Interprétation prestataire | Quand l'utiliser |
|---|---|---|
| 200 | Reçu et traité | Succès, ou doublon déjà traité |
| 400 | Requête invalide | Signature invalide, payload corrompu |
| 401 | Non authentifié | Signature absente ou fausse |
| 409 | Conflit | Traitement déjà en cours (optionnel) |
| 500 | Erreur serveur | Bug interne, déclenche un retry |
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.
Renvoyez 200 le plus vite possible, puis traitez en arrière-plan si le travail est long. Un webhook qui répond en plus de quelques secondes risque un timeout côté prestataire.
Stratégie de retries
Si vous répondez par une erreur 5xx ou ne répondez pas, le prestataire réessaie selon un backoff exponentiel.
| Tentative | Délai indicatif | Action backend |
|---|---|---|
| 1 | immédiat | Traiter ou logger l'échec |
| 2 | environ 1 minute | Vérifier idempotence |
| 3 | environ 5 minutes | Vérifier idempotence |
| 4 | environ 30 minutes | Alerter le support si toujours en échec |
| 5 et plus | jusqu'à 24 h | Réconciliation manuelle |
Exemple concret
Un client paie une commande de 40 000 FCFA. Le prestataire envoie payment.succeeded. Votre serveur lit le raw body, vérifie le HMAC, contrôle que event_id n'existe pas en base, insère l'événement, passe la commande de PENDING à PAID dans une transaction, puis renvoie 200. Une minute plus tard, un retial dupliqué arrive avec le même event_id : l'insertion échoue sur l'index unique, vous renvoyez 200 immédiatement, aucune double livraison. Total : un seul traitement effectif, zéro double débit.
Checklist sécurité webhook
| Contrôle | Statut attendu |
|---|---|
| Vérification HMAC sur raw body | obligatoire |
| Comparaison de signature en temps constant | obligatoire |
| Fenêtre anti-rejeu sur l'horodatage | obligatoire |
| Index unique sur event_id | obligatoire |
| Endpoint en HTTPS uniquement | obligatoire |
| Réponse 200 rapide, traitement async | recommandé |
| Journalisation de chaque événement | recommandé |
FAQ
Pourquoi ne pas se fier à la redirection du navigateur ?
Parce que le client peut fermer l'onglet ou perdre sa connexion avant le retour. Seul le webhook serveur à serveur garantit que vous êtes notifié du statut réel.
Sur quel contenu calculer le HMAC ?
Sur le corps brut de la requête, octet pour octet, avant tout parsing JSON. Re-sérialiser le JSON change les espaces et invalide la signature.
Comment éviter le double traitement ?
Un index unique sur event_id en base. Si l'événement existe déjà, renvoyez 200 sans retraiter. C'est la méthode la plus simple et la plus fiable.
Que renvoyer si la signature est invalide ?
Un code 401 ou 400, sans traiter l'événement. Ne renvoyez jamais 200 pour un webhook dont la signature ne correspond pas.
Combien de temps un prestataire réessaie-t-il ?
Généralement jusqu'à 24 heures avec un backoff exponentiel. Après échec total, prévoyez une réconciliation manuelle ou un endpoint de rejeu.
Discutons de votre projet. Kolonell construit des backends de paiement sécurisés avec vérification de signature et idempotence par défaut. 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.

