BroadNet
HTTP API & webhooksWebhooks

Livraison et fiabilité des webhooks : reprises, ordre, dead-letter

Mis à jour le 6 min de lectureFondé sur des normes
Réponse directe

Un webhook est un callback HTTP de serveur à serveur : la plateforme de messagerie fait un POST d'un événement vers l'URL du consommateur. La livraison fiable est at-least-once, si bien que les producteurs rejouent les livraisons échouées avec backoff exponentiel et jitter, mettent en dead-letter au-delà d'un plafond, et les consommateurs doivent être idempotents car les doublons et les événements hors ordre sont attendus.

SchémaComment un message A2P parvient au téléphone
  1. Votre applicationEnvoi via REST API ou SMPP
  2. Plateforme BroadNetValide, route et facture chaque message
  3. SMSC de l’opérateurConnexion directe opérateur
  4. Terminal de l'abonnéLe message est livré
L'accusé de réception (DLR) remonte le même chemin vers votre webhook
§01

Architecture d'un webhook

Un webhook inverse les rôles client/serveur habituels : au lieu que l'application interroge la plateforme de messagerie pour obtenir les mises à jour, la plateforme fait un POST d'un événement vers une URL que l'application a enregistrée — un changement de statut de livraison, un message entrant, un événement de compte. C'est la moitié push du modèle asynchrone dont la moitié pull est l'interrogation de GET /messages/{id}. L'endpoint du consommateur est un serveur HTTPS ordinaire qui doit accepter un POST, faire le minimum nécessaire pour enregistrer durablement l'événement, et renvoyer un 2xx rapidement. Un motif courant et important consiste à accuser réception à la réception et à traiter de façon asynchrone : l'endpoint écrit l'événement brut dans une file ou un journal, renvoie 200, et un worker séparé accomplit le vrai travail — de sorte qu'une logique métier lente ne provoque jamais l'expiration du délai du producteur ni une reprise.

Un POST de webhook de statut de livraison vers le consommateurhttp
POST /webhooks/dlr HTTP/1.1
Host: app.example
Content-Type: application/json
Webhook-Id: 8b1a...
Webhook-Timestamp: 1752916522
Webhook-Signature: v1,3f2b...

{
  "event": "message.delivered",
  "message_id": "01HZY8Q3M6",
  "status": "delivered",
  "occurred_at": "2026-07-19T09:15:25Z"
}
§02

Garanties de livraison : at-least-once

La livraison distribuée ne peut être simultanément exactly-once et disponible, si bien que presque tout système de webhook en production choisit at-least-once : le producteur rejoue jusqu'à voir un accusé de succès, en acceptant qu'un consommateur puisse occasionnellement recevoir le même événement plus d'une fois. Les extrêmes opposés sont pires — at-most-once perd des événements au premier échec, et le véritable exactly-once exige une coordination distribuée que le transport HTTP ne fournit pas. At-least-once impose une obligation au consommateur : il doit être idempotent, en dédupliquant sur un identifiant d'événement stable de sorte qu'un événement redélivré soit sans effet. Le contrat d'accusé est simple : tout 2xx signifie « arrête de rejouer » ; toute autre chose (ou un timeout) signifie « rejoue plus tard ».

§03

Reprises avec backoff exponentiel et jitter

Lorsqu'une livraison échoue ou expire, le producteur rejoue selon un calendrier qui croît exponentiellement — un délai de base doublé à chaque tentative jusqu'à un plafond — de sorte qu'un endpoint brièvement indisponible ne soit pas martelé. Point crucial, le délai inclut un jitter aléatoire : sans lui, une flotte d'événements ayant tous échoué durant la même panne se rejoueraient à l'unisson et produiraient un effet de troupeau synchronisé dès que l'endpoint se rétablit. Le full jitter (une valeur aléatoire entre zéro et la borne de backoff courante) répartit cette charge en douceur. Les reprises se poursuivent jusqu'au succès de l'événement ou jusqu'à un plafond total de tentatives ou de temps, après quoi l'événement est mis en dead-letter plutôt que rejoué indéfiniment.

Backoff exponentiel avec full jittertext
base = 2s ; factor = 2 ; cap = 3600s
attempt n bound = min(cap, base * factor^(n-1))
delay(n)        = random(0, bound)      # full jitter

n=1  bound  2s    delay in [0, 2s]
n=2  bound  4s    delay in [0, 4s]
n=3  bound  8s    delay in [0, 8s]
n=4  bound 16s    delay in [0, 16s]
...  until success or attempt/time cap -> dead-letter
§04

Délais d'expiration et ordre

Un producteur fixe un court délai d'expiration par livraison — couramment quelques secondes — et traite une expiration comme un échec : la connexion est abandonnée et l'événement est planifié pour une reprise. C'est la raison mécanique pour laquelle les consommateurs devraient accuser réception vite et traiter de façon asynchrone. L'ordre est la garantie la plus difficile. Comme les reprises et la livraison parallèle s'entremêlent, un flux de webhooks naïf n'est ordonné qu'au mieux : un événement antérieur rejoué peut arriver après un plus récent. Les systèmes qui ont besoin d'ordre livrent soit par clé en séquence (tous les événements pour un id de message sur une seule partition ordonnée, avec blocage en cas d'échec) soit, plus couramment, rendent la charge utile auto-ordonnée — chaque événement porte un timestamp et une séquence monotone pour que le consommateur puisse écarter une mise à jour périmée plutôt que dépendre de l'ordre d'arrivée.

Stratégies d'ordre pour les flux de webhooks
StratégieCommentCoût
Best-effortLivrer selon disponibilité, réessayer indépendammentLes événements peuvent arriver dans le désordre
Ordonné par cléSérialiser tous les événements pour un identifiant donné ; bloquer en cas d’échecBlocage en tête de file, débit inférieur
Charge utile auto-ordonnéeTransmettre horodatage + séquence ; le consommateur rejette les données périméesLogique du consommateur, mais sans blocage
§05

Dead-letter et santé d'endpoint

Un endpoint qui reste indisponible ne doit pas consommer indéfiniment la capacité de reprise. Une fois la politique de reprise épuisée, l'événement est déplacé vers une file de dead-letter — un magasin durable d'événements non livrables qu'un opérateur peut inspecter et rejouer une fois l'endpoint réparé — plutôt qu'abandonné silencieusement. Pour protéger les deux parties tant qu'un endpoint est en mauvaise santé, les producteurs suivent la santé de livraison par endpoint et appliquent un coupe-circuit : après une série soutenue d'échecs, le circuit s'ouvre et les nouveaux événements sont parqués ou rejetés rapidement au lieu d'être tentés, puis une sonde semi-ouverte teste périodiquement le rétablissement avant que la livraison normale ne reprenne. Cela empêche un consommateur mort de générer un arriéré de tentatives vouées à l'échec et laisse un consommateur en rétablissement remonter en charge en douceur. Le comportement concret de webhook et de reprise de BroadNet est documenté pour les développeurs.

§06

Construire un consommateur idempotent

Comme la livraison at-least-once garantit des doublons, c'est le consommateur — et non le producteur — qui possède les effets exactly-once. Le motif est un magasin de déduplication indexé sur un identifiant d'événement stable fourni par le producteur (un Webhook-Id ou un id d'événement, jamais le seul hash de la charge utile). À la réception, le consommateur vérifie s'il a déjà traité cet id ; si oui, il accuse réception avec un 2xx et ne fait rien d'autre ; sinon, il enregistre l'id et applique l'effet dans la même transaction, de sorte qu'un crash ne puisse pas appliquer l'effet sans enregistrer l'id. Combiné à des charges utiles auto-ordonnées, cela rend inoffensif un événement redélivré ou hors ordre, ce qui est précisément ce qui transforme un transport at-least-once en résultats métier exactly-once.

Consommateur idempotent (pseudo-code)text
on POST(event):
  verify_signature(event)          # reject if invalid
  id = event.headers['Webhook-Id']
  begin transaction:
    if seen(id): return 200        # duplicate -> no-op
    if event.sequence <= last_seq(event.message_id):
        mark_seen(id); return 200  # stale -> discard
    apply_effect(event)
    mark_seen(id); set_last_seq(...)
  commit
  return 200
FAQ

Questions fréquemment posées

Quelle garantie de livraison offrent les webhooks ?

At-least-once. Le producteur rejoue jusqu'à voir un accusé 2xx, si bien qu'un consommateur peut recevoir le même événement plus d'une fois. Exactly-once sur du HTTP simple exigerait une coordination distribuée que le transport ne fournit pas ; le consommateur est donc censé être idempotent et dédupliquer sur un id d'événement stable.

Pourquoi ajouter du jitter au backoff de reprise des webhooks ?

Sans jitter, chaque événement ayant échoué durant une même panne se rejoue à l'unisson au rétablissement de l'endpoint, créant un effet de troupeau synchronisé qui peut le remettre à terre. Le full jitter — un délai aléatoire entre zéro et la borne de backoff courante — répartit les reprises en douceur pour que l'endpoint en rétablissement voie une montée en charge progressive.

Comment un endpoint de webhook devrait-il répondre ?

Rapidement et avec un 2xx. Le motif recommandé consiste à enregistrer durablement l'événement brut, renvoyer 200 immédiatement, et traiter de façon asynchrone dans un worker séparé. Ainsi, une logique métier lente ne déclenche jamais l'expiration du délai de livraison du producteur ni une reprise inutile.

Les événements de webhook sont-ils livrés dans l'ordre ?

Par défaut, seulement au mieux — les reprises et la livraison parallèle peuvent s'entremêler, de sorte qu'un événement antérieur peut arriver après un plus récent. Pour obtenir l'ordre, sérialisez par clé (au prix d'un blocage en tête de file) ou rendez la charge utile auto-ordonnée avec un timestamp et un numéro de séquence pour que le consommateur écarte les mises à jour périmées.

Qu'est-ce qu'une file de dead-letter pour les webhooks ?

Un magasin durable pour les événements qui n'ont pas pu être livrés après épuisement de la politique de reprise. Au lieu de les abandonner silencieusement, le producteur les parque pour qu'un opérateur puisse inspecter les échecs et rejouer les événements une fois l'endpoint consommateur réparé.

Pourquoi appliquer un coupe-circuit aux endpoints de webhook ?

Un endpoint défaillant de façon persistante consommerait sinon la capacité de reprise avec des tentatives vouées à l'échec et bâtirait un arriéré. Le coupe-circuit s'ouvre après des échecs soutenus, parquant ou rejetant rapidement les nouveaux événements, puis utilise une sonde semi-ouverte pour tester le rétablissement avant de reprendre la livraison normale — protégeant à la fois le producteur et le consommateur en rétablissement.

Références

Spécifications sources

Les normes publiées sur lesquelles repose cette page. S'ouvre dans un nouvel onglet.

BNMaintenu par l'équipe d'ingénierie BroadNet·Messagerie de qualité opérateur depuis 2003
Placez-le sur le fil

Conçu avec une véritable liaison SMPP et une API REST.

Demandez un accès de test avec de véritables clés de test : envoyez d’abord à votre propre numéro vérifié, puis passez en production dans plus de 200 pays une fois que votre identité d’expéditeur et vos routes seront approuvées.