Skip to content

Borne les tentatives de livraison des webhooks et limite les alertes d'échec - #1739

Draft
Isalafont wants to merge 4 commits into
developfrom
feature/dpp-68-ts-webhooks-retry-borne-et-alertes-sur-echecs-persistants
Draft

Borne les tentatives de livraison des webhooks et limite les alertes d'échec#1739
Isalafont wants to merge 4 commits into
developfrom
feature/dpp-68-ts-webhooks-retry-borne-et-alertes-sur-echecs-persistants

Conversation

@Isalafont

@Isalafont Isalafont commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Ce que ça change

Aujourd'hui, un webhook dont l'endpoint ne répond plus est rejoué indéfiniment, et un endpoint injoignable ne laisse aucune trace dans l'historique. Cette PR :

  • borne les tentatives à 10 (fenêtre de relivraison d'environ 4 h), après quoi la livraison est abandonnée et marquée comme telle ;
  • enregistre les erreurs réseau (endpoint injoignable, timeout) comme des tentatives visibles, avec « Pas de réponse » à la place du code HTTP — c'est le cas qui ne produisait rien avant ;
  • pose des timeouts explicites sur l'appel HTTP : 5 s pour la connexion, 10 s pour la réponse ;
  • limite l'email d'alerte à un envoi par webhook toutes les 2 heures, et lève cette limite dès qu'un appel repasse en 2xx, pour qu'une panne ultérieure alerte à nouveau ;
  • expose abandoned_at dans l'API et affiche un badge « Abandonné » dans l'historique des appels.

Comment tester

1. L'écran des tentatives (le plus rapide)

bin/rails db:seed

Se connecter comme développeur API Entreprise :
http://localhost:3000/local-sign-in?email=api-entreprise@yopmail.com

Aller dans Développeurs > Webhooks, puis « Voir les appels » sur le webhook http://localhost:3000/dummy/failing/webhooks.

Les seeds créent 6 tentatives qui couvrent tous les états de l'écran :

Événement Attendu
Soumission badge vert « Succès », statut 200
Validation badge rouge « Échec », statut 500
Demande de modification badge rouge « Échec », statut 422
Refus badge rouge « Échec », « Pas de réponse » au lieu d'un code HTTP
Validation badge orange « Abandonné », statut 500
Soumission badge orange « Abandonné », statut 422

Cliquer sur une tentative abandonnée : le détail affiche le même badge, le payload envoyé et la réponse reçue.

2. Les tests automatisés

bundle exec rspec spec/jobs/deliver_authorization_request_webhook_job_spec.rb \
                  spec/interactors/developer \
                  spec/models/webhook_spec.rb spec/models/webhook_attempt_spec.rb \
                  spec/helpers/webhooks_helper_spec.rb \
                  spec/services/webhook_http_service_spec.rb \
                  spec/requests/api/v1/webhook_attempts_controller_spec.rb \
                  spec/lib/seeds_spec.rb

bundle exec cucumber features/developpeurs/webhooks.feature

Ils couvrent les comportements qui ne se voient pas à l'écran : arrêt des relances après la 10ᵉ tentative, marquage de l'abandon, message Sentry distinct, endpoint injoignable et timeout enregistrés avec un statut vide, envoi unique de l'alerte sous charge concurrente, et remise à zéro de la limite quand l'endpoint se rétablit.

3. En recette, sur sandbox

La branche n'est pas mergée, donc seul sandbox peut la prendre : staging et production déploient toujours main.

gh workflow run https-deploy-sandbox.yaml --ref feature/dpp-68-ts-webhooks-retry-borne-et-alertes-sur-echecs-persistants

Il faut ensuite un endpoint qui accepte d'abord les appels, puis tombe en panne : DataPass teste l'URL au moment de l'activation et refuse un webhook dont l'endpoint ne répond pas 2xx. https://webhook.site fait exactement ça — il fournit une URL jetable qui répond 200, et son bouton « Edit » permet de basculer la réponse en 500 quand on veut.

  1. Sur sandbox, Développeurs > Webhooks : créer le webhook avec l'URL webhook.site, cocher Soumission et Validation, puis l'activer.
  2. Sur webhook.site, passer la réponse en 500.
  3. Avec un compte demandeur, soumettre une demande d'habilitation API Entreprise.

Ce qu'on doit observer :

  • l'appel en échec apparaît immédiatement dans l'historique des appels ;
  • au bout d'environ 6 minutes (5ᵉ tentative), un email d'alerte arrive aux développeurs du type d'habilitation ;
  • si on fait valider la demande dans la foulée, ce second événement échoue aussi mais aucun deuxième email ne part — c'est la limite de 2 heures ;
  • si on repasse webhook.site en 200 le temps d'un événement réussi, puis à nouveau en 500, un nouvel email repart sans attendre la fin des 2 heures.

L'abandon arrive naturellement au bout d'environ 4 h. Pour le voir tout de suite, un compte admin peut relancer les jobs en attente depuis /workers jusqu'à la 10ᵉ tentative : la dernière prend alors le badge « Abandonné », et GET /api/v1/webhooks/:id/attempts renvoie abandoned_at.

Le cas « pas de réponse » demande un endpoint qui met plus de 10 secondes à répondre (délai de réponse réglable sur webhook.site, ou stub perso derrière un tunnel). Résultat attendu : une tentative affichée « Pas de réponse » au lieu d'un code HTTP.

Doc

docs/webhooks.md et le tutoriel développeurs sont à jour : politique de retry, tableau des délais avec temps cumulé, abandon, limite de l'alerte.

Points d'attention pour la relecture

  • 4 commits autoporteurs, relisibles un par un.
  • Le seuil d'alerte tombe désormais à la 5ᵉ tentative réelle, contre la 4ᵉ auparavant : l'ancien compteur attempts valait executions + 1. C'est un alignement avec ce que la doc annonçait déjà.
  • Un audit RGAA des écrans concernés a été fait : les 4 non-conformités relevées préexistent à cette PR et sortent de son périmètre.

Les livraisons échouées étaient rejouées indéfiniment, sans distinguer
« en cours de réessai » de « définitivement perdu ». Elles sont désormais
bornées à 10 tentatives, et la dernière est marquée `abandoned_at` avec un
message Sentry dédié.

Les erreurs réseau Faraday, jusqu’ici remontées brutes, deviennent des
tentatives sans code HTTP et suivent la même borne. WebhookHttpService reçoit
des timeouts explicites.
L’alerte partait à la 5e exécution de chaque job : un incident touchant
plusieurs demandes déclenchait autant d’emails que de livraisons en échec.

Une seule alerte est désormais envoyée par webhook et par fenêtre de 2 heures
(WEBHOOK_FAILURE_ALERT_THROTTLE_HOURS), réservée sous verrou pessimiste pour
tenir face aux jobs concurrents.
Une livraison abandonnée s’affichait comme un simple « Échec » : impossible de
savoir si DataPass allait retenter. L’écran distingue maintenant Succès, Échec
et Abandonné, via un partial `_result_badge` partagé entre la liste et le détail.

`abandoned_at` est également exposé sur l’API v1 et dans l’OpenAPI.

- Mise à jour de la doc et du tutoriel
Les seeds ne produisaient que des succès et des échecs : vérifier le rendu d’un
abandon demandait de manipuler la base à la main.

Un webhook API Entreprise de prévisualisation porte les six états utiles de
l’écran, recréés à chaque exécution des seeds sans doublon.

Accès : /local-sign-in?email=api-entreprise@yopmail.com
@linear

linear Bot commented Aug 20, 2026

Copy link
Copy Markdown

DPP-68

@Isalafont
Isalafont force-pushed the feature/dpp-68-ts-webhooks-retry-borne-et-alertes-sur-echecs-persistants branch from 46a89ec to 72443a8 Compare August 20, 2026 12:41
@Isalafont Isalafont changed the title Feature/dpp 68 ts webhooks retry borne et alertes sur echecs persistants Feature/dpp 68 webhooks retry borne et alertes sur echecs persistants Aug 20, 2026
@Isalafont Isalafont self-assigned this Aug 20, 2026
@Isalafont Isalafont added the enhancement New feature or request label Aug 20, 2026
@Isalafont Isalafont changed the title Feature/dpp 68 webhooks retry borne et alertes sur echecs persistants Borne les tentatives de livraison des webhooks et limite les alertes d'échec Aug 20, 2026
@Isalafont
Isalafont requested a review from jbfeldis August 20, 2026 12:57
@skelz0r

skelz0r commented Aug 20, 2026

Copy link
Copy Markdown
Member

Vu la régression que vous introduisez (si j'ai bien compris au bout de 4h vous passez un event en abandonné non rejouable ?) il faut que l'on itère coté API E/P pour gérer la potentielle perte de données. 4h c'est très très court (genre le site tombe la nuit on perd de l'info). On est très rapide pour fixer chez nous (< 24h généralement), une administration on peut tabler sur une bonne semaine -> de ce fait, tous les webhooks "perdus" font que le système perd un intérêt majeur : sa résilience.

question: pourquoi introduire cette régression produit ? je ne sais pas exactement ce que vous voulez résoudre comme problème (le ticket est monstrueusement gros et il n'y a pas de why ? modulo Chantier sécurité/fiabilité qui n'explique pas vraiment en quoi ça sécurise ni fiabilise).

btw

un endpoint injoignable ne laisse aucune trace dans l'historique

c'est faux non ?

@Isalafont

Copy link
Copy Markdown
Contributor Author

Vu la régression que vous introduisez (si j'ai bien compris au bout de 4h vous passez un event en abandonné non rejouable ?) il faut que l'on itère coté API E/P pour gérer la potentielle perte de données. 4h c'est très très court (genre le site tombe la nuit on perd de l'info). On est très rapide pour fixer chez nous (< 24h généralement), une administration on peut tabler sur une bonne semaine -> de ce fait, tous les webhooks "perdus" font que le système perd un intérêt majeur : sa résilience.

question: pourquoi introduire cette régression produit ? je ne sais pas exactement ce que vous voulez résoudre comme problème (le ticket est monstrueusement gros et il n'y a pas de why ? modulo Chantier sécurité/fiabilité qui n'explique pas vraiment en quoi ça sécurise ni fiabilise).

btw

un endpoint injoignable ne laisse aucune trace dans l'historique

c'est faux non ?

Merci pour le retour. la PR ne rend pas les événements abandonnés “non rejouables” et ne supprime pas le payload.
Après épuisement des retries automatiques, la tentative est marquée comme abandonnée, mais elle reste visible dans l’historique et rejouable manuellement depuis l’interface.

Le “why” vient de l’incident de juillet, ou un développeur a reçu plusieurs centaines d’emails liés aux webhooks en échec/retry durant une nuit. L’objectif est donc de borner les retries automatiques et les alertes pour éviter une boucle infinie et du bruit opérationnel, pas de perdre de la donnée.

Après effectivement, on peut revoir la fenêtre de temps / nombre de retry avant d'abandonnée / arrêter les retry.

Le comportement cible côté produit est : historique conservé, payload conservé, rejeu possible, mais retries automatiques bornés.

D'ailleurs si tu as d'autres idées je suis preneuse.

@skelz0r

skelz0r commented Aug 20, 2026

Copy link
Copy Markdown
Member

Merci pour le retour. la PR ne rend pas les événements abandonnés “non rejouables” et ne supprime pas le payload.

Ok j'avais cru comprendre au milieu du ticket que le bouton rejouable était omis sur les cas abandonnés. Après cliquer sur 12883218936 payloads en défaut à la main c'est pas non plus la folie 😅

Le “why” vient de l’incident de juillet, ou un développeur a reçu plusieurs centaines d’emails liés aux webhooks en échec/retry durant une nuit.

Je suis étonné de ce comportement, on a eu historiquement l'API E/P qui a planté plusieurs fois sur les webhooks de manière continue et on ne recevait qu'un email à chaque fois 🤔

Donc en gros c'est l'envoi d'email le souci, du coup ici il suffit juste de n'envoyer qu'un email par tranche de X heures, ce qui est cantonne le fix à quelques lignes (avec un compteur redis) vs ce que tu proposes qui in-fine fait pas mal de lignes.

@Isalafont
Isalafont removed the request for review from jbfeldis August 24, 2026 09:34
@Isalafont
Isalafont marked this pull request as draft August 24, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants