Un paiement n'aboutit pas au moment où vous l'appelez : l'acheteur passe par sa banque, valide un 3-D Secure, revient. Le résultat vous parvient donc par webhook, et c'est lui qui fait foi — pas la redirection du navigateur, qu'un acheteur peut interrompre en fermant son onglet. Le sondage est un repli, pas la source de vérité.
Vous déclarez vos URL de réception depuis l'API, avec une liste explicite d'événements. N'abonnez que ce que vous traitez : chaque événement inutile est une occasion de bug et une charge sur votre serveur.
L'URL doit être en HTTPS public, sur le port 443. localhost et les adresses privées sont refusés dès l'enregistrement — en développement, passez par un tunnel.
La signature. Chaque livraison porte X-CHARI-SIGNATURE : un HMAC-SHA256 en hexadécimal minuscule, calculé sur la chaîne horodatage + "." + corps brut. L'horodatage est dans X-CHARI-TIMESTAMP, en millisecondes depuis epoch.
Quatre règles font toute la sécurité de la vérification : calculez le HMAC sur les octets bruts avant tout parsing — un JSON re-sérialisé ne produit plus la même signature ; rejetez un horodatage décalé de plus de ±5 minutes, comme nous le faisons ; comparez en temps constant (timingSafeEqual), jamais avec === ; et pendant une rotation de secret, acceptez l'une ou l'autre des deux signatures.
Pendant une rotation, tant que l'ancien secret est dans sa fenêtre de grâce, nous envoyons le même corps signé deux fois : X-CHARI-SIGNATURE avec l'ancien secret et X-CHARI-SIGNATURE-NEXT avec le nouveau. Vérifiez avec le secret que vous détenez, puis basculez.
Dédupliquez sur Chari-Event-Id, pas sur Chari-Webhook-Id. C'est la distinction qui coûte le plus cher quand on se trompe : un même événement logique peut être livré plusieurs fois, chaque tentative reçoit son propre Chari-Webhook-Id — qui change donc à chaque essai — mais toutes portent le même Chari-Event-Id. Se dédupliquer sur le mauvais, c'est traiter deux fois le même paiement.
La livraison est au moins une fois, et peut arriver dans le désordre : vous verrez des répétitions, c'est par conception. Ne répondez 2xx qu'une fois la mise à jour métier et l'enregistrement de déduplication tous deux validés — toute réponse non-2xx est retentée. Faites le travail lourd en tâche de fond : un traitement de dix secondes finit par déclencher des relectures et, à terme, la suspension de votre endpoint. La première tentative part immédiatement, puis 1 min → 5 min → 30 min → 1 h → toutes les 6 h, jusqu'à 16 tentatives sur environ 72 heures ; au-delà, la livraison est marquée échouée. Après une indisponibilité plus longue, réconciliez par GET /v1/transactions plutôt que d'attendre un webhook qui ne reviendra plus.
Vos champs metadata et externalId reviennent sur les événements de la ressource qui les portait : c'est ce qui vous permet de rapprocher sans stocker nos références.