Aller au contenu

Nouvelles tentatives et ordre

Ce qui se passe quand votre endpoint est en panne, et pourquoi un événement arrive deux fois.

Votre endpoint tombera en panne un jour ou l'autre, et Wajub fera de nouvelles tentatives. C'est la partie facile. La partie difficile, c'est qu'une nouvelle tentative exécute votre handler une seconde fois sur un événement qu'il a peut-être déjà traité, et s'il expédie une commande à chaque exécution, il l'expédie deux fois.

Cette page couvre ce que Wajub garantit, ce qu'il ne garantit pas, et la forme de handler qui résiste aux deux.

Ce que Wajub garantit

Au moins une fois, pas exactement une fois. Chaque événement atteint votre endpoint au moins une fois. Certains l'atteindront plusieurs fois. Concevoir pour une livraison exactement une fois, c'est concevoir pour quelque chose qu'aucun système de webhooks ne fournit.

Aucune garantie d'ordre. Les événements sont livrés au fur et à mesure de leur production, via une file, avec des nouvelles tentatives intercalées. Un payment.succeeded peut arriver avant le payment.processing qui l'a précédé. Ne déduisez jamais l'état d'un paiement de l'ordre d'arrivée des événements. Lisez l'état dans le corps de l'événement, ou récupérez le paiement.

Quand une livraison compte comme un échec

Une livraison réussit quand votre endpoint répond avec n'importe quel 2xx en moins de 10 secondes. Toute autre issue est un échec.

Votre réponseCe qui se passe
2xxLivré. Rien de plus.
5xx, ou une erreur réseauNouvelle tentative, selon le calendrier ci-dessous
408 ou 429Nouvelle tentative, selon le calendrier ci-dessous
Tout autre 4xxAucune nouvelle tentative. Traité comme un rejet définitif
Aucune réponse en 10 secondesNouvelle tentative
Une redirection 3xxNouvelle tentative. Les redirections ne sont jamais suivies, une redirection n'aboutit donc jamais à une livraison

Le calendrier des nouvelles tentatives

Cinq tentatives au total, puis la livraison est marquée comme échouée.

TentativeEnvoi
1immédiatement
230 secondes après l'échec de la tentative 1
31 minute plus tard
45 minutes plus tard
510 minutes plus tard

La séquence complète s'étend sur environ seize minutes. Après le cinquième échec, Wajub s'arrête. Votre endpoint n'est pas désactivé : le prochain événement sera quand même tenté, et vous pouvez rejouer à la main celui qui a échoué depuis Konsole ou avec POST /events/{id}/resend.

Dédupliquer : deux clés, deux significations

Chaque livraison porte les deux. Elles ne sont pas interchangeables, et choisir la mauvaise produit un bug que vous ne verrez pas avant d'ajouter un second endpoint.

id dans le corps, une valeur evt_…, identifie l'événement. Il est identique à chaque nouvelle tentative, et identique pour chaque endpoint qui reçoit cet événement.

X-Wajub-Delivery-Id dans les en-têtes, une valeur whd_…, identifie la livraison vers un endpoint. Il reste stable sur les cinq tentatives, et diffère pour chaque endpoint.

Laquelle stocker

Dédupliquez sur l'id du corps quand le traitement doit avoir lieu une fois par événement métier, ce qui est presque toujours le cas : expédier une commande, créditer un compte, envoyer un reçu. Utilisez X-Wajub-Delivery-Id quand vous suivez la santé des livraisons par endpoint plutôt que les effets métier.

La forme du handler

Quatre étapes, dans cet ordre. Tout l'enjeu est dans l'ordre.

# The same event, arriving a second time after a timeout.
# Body id is identical, delivery id is not.
X-Wajub-Delivery-Id: whd_7Yh2MpL4tRb3nP8sZcXv
{"id":"evt_aio5DpN577tNU2vOxdmuZGhT","event":"payment.succeeded","data":{…}}

Marquez l'événement comme traité avant d'effectuer le travail, pas après. Si vous le marquez après, deux nouvelles tentatives arrivées à peu d'intervalle peuvent toutes deux passer la vérification et toutes deux s'exécuter.

Rapprocher l'événement de la requête qui l'a provoqué

Le corps contient la requête qui a produit l'événement :

JSON
{
"id": "evt_aio5DpN577tNU2vOxdmuZGhT",
"event": "payment.succeeded",
"data": {
"id": "trx_CSUGajfv9xh0XQ5wu2lx",
"status": "succeeded"
},
"livemode": true,
"pending_webhooks": 0,
"api_version": "2026-09-01",
"request": {
"id": null,
"idempotency_key": "idem_kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0"
},
"created": "2026-01-15T10:30:00Z"
}

request décrit l'enregistrement de l'événement, pas votre appel API. request.id vaut null, et request.idempotency_key est une valeur idem_… générée au moment où l'événement a été stocké. Aucun des deux ne vous renvoie l'en-tête Idempotency-Key que vous avez envoyé.

Associez plutôt un événement à une commande grâce à la ressource : data.reference est la reference que vous avez fournie à la création du paiement, et data.metadata est à votre disposition. Les deux survivent à chaque nouvelle tentative, car ils font partie de la ressource et non de la livraison.

`livemode`, pas `sandbox`

L'enveloppe du webhook utilise livemode, qui est l'inverse du champ sandbox renvoyé par l'API REST. Un événement sandbox porte "livemode": false.

api_version est la version courante de la plateforme au moment où l'événement a été enregistré, et le corps suit la forme de cette version. Fixer votre compte ou une requête sur une version plus ancienne change ce que renvoient vos appels API, jamais ce que reçoit votre endpoint.

Inspecter et rejouer

Chaque tentative est enregistrée avec son code de statut, son corps de réponse et sa latence dans Konsole. Une fois votre endpoint corrigé, vous pouvez rejouer une seule livraison, ou jusqu'à 100 livraisons échouées d'un coup, avec le payload d'origine et un en-tête fraîchement signé.

Webhook deliveries

last hour
MethodEndpointStatusDurationAttempt
POST/webhooks/wajub50010021 ms1 of 5
POST/webhooks/wajub500240 ms2 of 5
POST/webhooks/wajub200142 ms3 of 5

Que pensez-vous de ce contenu ?