Changelogs de webhooks : le changement cassant non demandé
6 min de lecture
Un changelog d’API REST existe parce qu’un appelant peut choisir de rejeter une réponse qu’il ne comprend pas, ou au moins enregistrer une erreur assez bruyante pour que quelqu’un le remarque. Un récepteur de webhook fait rarement l’un ou l’autre. Il reçoit un POST, lit les champs qu’il attend, et si un champ a bougé, changé de type ou disparu, l’endpoint plante soit silencieusement dans une tâche en arrière-plan que personne ne surveille, soit, pire, continue de tourner avec une valeur fausse qu’il n’a jamais validée. Qu’est-ce qu’un changement cassant couvre la définition générale ; un payload de webhook a besoin de sa propre réponse, parce que le mode d’échec est différent de celui d’un endpoint que quelqu’un appelle exprès.
Pourquoi un changement de payload de webhook casse-t-il différemment d’un changement de réponse d’API ?
Parce que le sens de la requête est inversé. Un appelant REST initie l’appel et peut ajouter un en-tête de version, réessayer sur un 4xx, ou lire un avis de dépréciation dans la réponse. Un récepteur de webhook n’a initié aucune de ces choses : votre serveur a décidé d’envoyer, a décidé quand, et a décidé de la forme du corps. Le seul levier du récepteur est la validation qu’il a écrite quand l’intégration a été construite, et la plupart des intégrations sont construites une fois, fonctionnent, et ne sont jamais revues jusqu’à ce qu’elles cassent. Cette asymétrie est toute la raison pour laquelle un changement de payload de webhook mérite plus de prudence que le même changement dans un corps de réponse qu’un appelant a activement demandé.
Qu’est-ce qui compte vraiment comme changement cassant dans un payload de webhook ?
| Changement | Cassant pour la plupart des récepteurs |
|---|---|
| Ajouter un nouveau champ | Non, si les récepteurs ignorent les champs inconnus (vérifiez cette hypothèse, ne la supposez pas) |
| Supprimer un champ | Oui, si quelque chose le lit |
| Renommer un champ | Oui, fonctionnellement identique à supprimer l’ancien |
| Changer le type d’un champ (chaîne vers objet) | Oui, presque toujours |
| Réordonner les champs dans le corps JSON | Non, pour tout récepteur qui parse par clé, ce qui devrait être tous |
| Changer le nom ou le type d’événement | Oui, si les récepteurs filtrent ou routent dessus |
La ligne « ajouter un champ est sûr » est celle sur laquelle les équipes s’appuient le plus et celle qu’il vaut le mieux vérifier plutôt que supposer. Un parser JSON permissif ignore les champs inconnus par défaut, mais un récepteur qui désérialise dans un schéma strict, plusieurs langages typés le font sans configuration supplémentaire, peut rejeter tout le payload dès qu’un champ inattendu apparaît. Ajouter un champ n’est sûr pour votre webhook que si vous savez comment les récepteurs parsent, pas parce que JSON en soi est permissif.
Comment versionner un payload de webhook ?
À peu près comme pour une réponse d’API, avec une nuance : le récepteur n’envoie jamais de
requête, il ne peut donc pas demander de version, et c’est l’émetteur qui doit l’indiquer. Elle
peut aller dans le corps ou dans un en-tête de requête de la livraison elle-même ;
les livraisons de GitHub
portent X-GitHub-Event et X-GitHub-Hook-ID, et la
spécification Standard Webhooks
place ses métadonnées dans des en-têtes webhook-*. Un champ de version dans le payload ("payload_version": 2) est
l’option la moins chère et fonctionne quand les récepteurs sont prêts à brancher dessus. Un type
d’événement versionné (invoice.updated devient invoice.updated.v2 comme un événement distinct
auquel un récepteur s’abonne volontairement) demande plus de travail à construire mais signifie
que l’ancienne forme continue d’arriver à qui n’a jamais migré, ce qui compte plus ici que pour un
endpoint REST parce que vous ne pouvez pas appeler chaque récepteur pour lui dire de mettre à
jour. Un réglage par abonnement, choisi à l’enregistrement de l’endpoint du webhook, anticipe la
décision au lieu de brancher à chaque livraison, et c’est le bon choix quand vous avez déjà un
enregistrement d’abonnement auquel l’attacher.
POST /endpoint-recepteur
{
"event": "invoice.updated",
"payload_version": 2,
"data": { "invoice_id": "inv_123", "status": "paid" }
}
Comment savez-vous même qui écoute ?
Pire que la version équivalente de ce problème dans un changelog d’API, parce qu’un webhook n’a pas de journal de requêtes entrantes de votre côté qui nomme l’appelant ; vous n’avez que votre propre journal de livraison sortante, qui vous dit qu’un endpoint a reçu un 200, pas ce qu’il a fait du corps. Suivez au minimum deux choses : chaque endpoint enregistré avec une responsable, la même discipline que les changelogs d’API interne recommandent pour les consommateurs internes, et votre taux d’échec de livraison par endpoint après un changement de payload. Un pic de réponses 4xx ou 5xx d’un endpoint juste après un changement est ce qui se rapproche le plus d’une stack trace que vous obtiendrez, et c’est souvent le seul signal qu’un récepteur a cassé, parce que l’équipe qui l’exploite peut mettre des jours à le remarquer.
Un changelog de webhooks devrait-il être séparé du changelog d’API ?
Une section séparée sur la même page, pas une publication séparée. Un changelog d’API établit déjà qui le lit et comment on s’y abonne ; un changement de payload de webhook appartient au même flux, étiqueté avec assez de clarté pour qu’une développeuse côté récepteur qui scanne pour « est-ce que ça affecte mon intégration » puisse le filtrer, parce qu’une consommatrice de webhook n’a souvent aucune autre raison de vérifier un changelog d’API général et ne le trouvera que si quelqu’un l’y renvoie directement.
À quoi devrait ressembler une fenêtre de dépréciation raisonnable pour un payload de webhook ?
Plus longue que la dépréciation REST équivalente, parce que la migration côté récepteur signifie
généralement qu’une deuxième équipe, avec qui vous n’avez peut-être pas de contact direct, doit le
remarquer, le planifier et le livrer sans urgence propre. Un mois est un minimum raisonnable pour
un champ que le récepteur parse probablement encore avec une bibliothèque permissive ; trois mois
ou plus sont plus sûrs pour une suppression de champ qu’un schéma strict rejetterait complètement.
Envoyez l’ancienne et la nouvelle forme ensemble pendant la fenêtre quand c’est faisable (l’ancien
champ status et son remplaçant de la version 2 dans le même payload), parce qu’un
récepteur qui lit l’ancien champ continue de fonctionner sans toucher à son code, et un qui a déjà
migré ignore simplement le champ dont il n’a plus besoin.
FAQ
Les consommateurs de webhooks doivent-ils accuser réception d’un changement de payload avant sa mise en ligne ? Aucun mécanisme d’accusé de réception n’existe par défaut, et c’est exactement pourquoi la fenêtre de dépréciation compte plus ici que pour une API REST : personne ne confirme être prêt, donc la fenêtre doit être assez longue pour que la plupart des récepteurs migrent à leur propre rythme avant que l’ancienne forme disparaisse.
Est-il jamais sûr d’ajouter des champs inconnus sans préavis ? Seulement une fois que vous avez vérifié, pas supposé, que vos récepteurs parsent de façon permissive. Une entrée de changelog coûte peu et enlève l’incertitude ; ajouter des champs en silence en supposant que « les parsers JSON ignorent les extras » casse tout récepteur avec une désérialisation stricte.
Quel est le moyen le plus rapide de détecter un récepteur de webhook cassé après un changement de payload ? Un taux d’échec de livraison par endpoint, observé dans les heures juste après le changement. Cela ne vous dira pas ce qui a cassé, seulement que quelque chose a cassé, mais c’est le signal le plus précoce et souvent le seul que vous obtiendrez.
La logique de retry aide-t-elle les récepteurs à survivre à un changement de payload ? Non. Un retry renvoie le même nouveau payload ; il ne revient pas à une forme que le récepteur peut parser. Un changement de payload casse un récepteur à la première livraison et à chaque retry suivant de façon identique.
Les affirmations techniques de cet article n'ont pas été vérifiées de façon indépendante. Si quelque chose est faux, dis-le-nous et nous le corrigerons.