Callbacks DLR en A2P SMS : conception idempotente face aux doublons et au désordre
Concevez la réception des callbacks DLR comme un historique auditable et une projection d’état idempotente, capable de gérer les tentatives répétées, les doublons, les événements tardifs et les séquences dans le désordre.

La question opérationnelle : que se passe-t-il si le callback arrive deux fois, en retard ou dans un autre ordre ?
Un callback de reçu de livraison (DLR) ne doit pas être traité comme une mise à jour unique, ordonnée et définitive. Dans une intégration HTTP, une communication peut échouer avant que l’émetteur obtienne la réponse, et la requête peut être répétée. La RFC 9110 définit l’idempotence comme la propriété selon laquelle plusieurs requêtes identiques produisent le même effet prévu qu’une seule requête.
En pratique, le récepteur doit pouvoir accepter une même notification plusieurs fois sans la comptabiliser plusieurs fois ni modifier indûment l’état du message. Il doit également pouvoir enregistrer un reçu arrivé après un autre événement précédemment observé, sans effacer les éléments de preuve ni supposer que l’ordre de réception équivaut à l’ordre réel de traitement dans la chaîne de messagerie.
La règle opérationnelle est simple : enregistrez chaque réception, déduisez l’état actuel à l’aide de règles explicites et gardez ces deux éléments séparés. L’historique répond à ce qui a été reçu et à quel moment ; la projection d’état répond au résultat opérationnel calculé par le système à partir des éléments disponibles.
- Ne supposez pas une livraison parce qu’un callback a été reçu par votre endpoint.
- Ne supposez pas que le dernier callback reçu représente nécessairement le dernier fait survenu sur le réseau.
- N’appliquez pas de mise à jour irréversible en vous fondant uniquement sur la destination, le contenu ou une correspondance approximative.
- Répondez de manière sûre aux répétitions : une nouvelle tentative ne doit pas générer d’effets comptables, analytiques ou opérationnels en double.

Ce qu’un DLR représente et ce qu’il ne représente pas
Dans SMPP, un DLR est demandé dans submit_sm au moyen de registered_delivery. Le reçu peut être renvoyé à l’ESME par deliver_sm ou data_sm. La disponibilité des reçus dépend donc de leur demande ainsi que de la configuration ou du comportement de la plateforme de messagerie.
SMPP distingue un SMSC Delivery Receipt d’une notification intermédiaire par l’encodage de esm_class. Les notifications intermédiaires constituent un type distinct et leur prise en charge dépend de l’implémentation du SMSC ; elles ne doivent pas être automatiquement interprétées comme des résultats finaux.
Un DLR renseigne sur l’état signalé par la chaîne de messagerie dans ce flux. Il ne constitue pas une vérification indépendante qu’une personne a vu, lu ou compris le contenu sur son terminal. Dans la sémantique SMPP, DELIVERED indique la livraison à la destination ; la norme citée ne comporte pas d’état de lecture humaine ou de contenu affiché.
- Distinguez l’acceptation de submit_sm, qui confirme la présentation au système répondant, d’un DLR ultérieur.
- Enregistrez si un DLR a été demandé et selon quelle modalité de registered_delivery lorsque cette donnée est disponible.
- Étiquetez le résultat comme un état rapporté par la messagerie, et non comme une preuve de lecture par le destinataire.
- Traitez les notifications intermédiaires comme des événements distincts des états finaux.

Modèle minimal de données pour une traçabilité utile
La conception doit préserver les données nécessaires pour reconstruire une décision. Pour chaque message envoyé, conservez un identifiant interne immuable et l’identifiant externe renvoyé par le système de messagerie. Dans SMPP, receipted_message_id identifie le message concerné par le reçu et correspond à l’identifiant opaque message_id renvoyé lors de la confirmation de la présentation initiale.
Enregistrez la destination sous une représentation normalisée et conservez également le contexte d’adressage reçu ou envoyé. E.164 définit le plan public international de numérotation ; sa normalisation aide à éviter les variations de format, mais ne doit pas remplacer les valeurs d’origine nécessaires au débogage.
Le format historique d’un DLR dans short_message peut inclure l’identifiant, les dates de présentation et de finalisation, l’état et l’erreur. Toutefois, ses particularités peuvent être spécifiques à la passerelle ou au SMSC. Extrayez donc des champs normalisés pour l’exploitation, mais conservez toujours la charge utile d’origine.
- Message : ID interne, ID externe ou message_id, origine, destination normalisée, valeurs d’adressage d’origine, route ou contexte d’envoi et date de présentation.
- Événement reçu : ID d’événement du fournisseur s’il existe, date et heure de réception, date et heure rapportées par l’émetteur lorsqu’elles existent, type d’événement, état brut, code d’erreur brut et payload d’origine.
- Projection : état calculé, motif de la décision, événement ou événements étayant cette décision et date de mise à jour.
- Audit : version du parseur ou des règles appliquées, résultat de la corrélation et toute exception détectée.
Corrélation : privilégiez les clés stables et rejetez les correspondances faibles
La corrélation entre le message d’origine et le DLR doit d’abord reposer sur l’identifiant externe attribué par le SMSC. SMPP définit receipted_message_id comme l’identifiant du message auquel le reçu correspond. De plus, query_sm utilise le message_id attribué par le SMSC avec l’adresse source comme mécanisme de correspondance.
N’utilisez pas la destination, le texte, une fenêtre temporelle ou l’expéditeur comme substituts automatiques à l’ID externe. Ces attributs peuvent se répéter entre des messages légitimes et conduire à une attribution erronée. Une corrélation incorrecte est plus dommageable qu’un événement en attente : elle peut transformer l’état d’un autre message en livraison ou en échec qui ne lui a jamais appartenu.
Lorsque la corrélation n’est pas concluante, enregistrez le callback sans le perdre et dirigez-le vers une file ou un registre d’exceptions. Cette décision permet d’examiner les changements de format, les ID tronqués, les variations d’encodage ou d’autres particularités d’une intégration sans contaminer la projection des messages connus.
- Premier choix : associez receipted_message_id au message_id enregistré lors de l’acceptation de l’envoi.
- Conservez la valeur externe exactement telle qu’elle a été reçue, en plus de toute forme normalisée requise par l’intégration.
- Utilisez l’origine, la destination, les dates et la route comme validations auxiliaires, et non comme clé d’attribution unique.
- S’il existe plusieurs candidats ou aucun, marquez la corrélation comme non concluante et ne matérialisez pas l’état sur un message précis.
Modèle idempotent pour recevoir et traiter les callbacks
L’idempotence n’exige pas d’ignorer toute répétition. Elle permet de conserver chaque requête reçue comme élément de preuve tout en empêchant qu’une répétition modifie plusieurs fois le résultat opérationnel. La RFC 9110 précise qu’un serveur peut consigner chaque requête individuelle même si l’effet prévu de l’opération est idempotent.
Mettez en œuvre deux couches. La première est un journal des réceptions, de préférence immuable, qui stocke le payload, les en-têtes pertinents disponibles, l’instant de réception et le résultat du parseur. La seconde est l’application des effets : déduplication, corrélation et calcul d’état. Seule cette seconde couche doit être protégée contre l’application répétée d’un même événement logique.
Si l’émetteur fournit un ID d’événement stable, utilisez-le comme clé de déduplication dans le périmètre approprié de l’intégration. S’il n’existe pas, créez une empreinte à partir d’attributs stables présents dans le callback, conservez les composants utilisés et gardez le payload d’origine. Ne fondez pas l’empreinte sur des champs susceptibles de changer en raison d’une transformation locale ou sur des attributs ambigus sans documenter le risque.
- 1. Recevez le callback et persistez la réception avant d’exécuter des effets métier.
- 2. Validez et extrayez les champs disponibles sans éliminer le payload d’origine.
- 3. Déterminez s’il existe un ID d’événement stable ; sinon, calculez une empreinte documentée pour l’événement logique.
- 4. Insérez ou détectez l’événement de manière atomique dans le registre des événements logiques.
- 5. Corrélez par ID externe et appliquez les règles de transition une seule fois par événement logique.
- 6. Renvoyez une réponse HTTP cohérente après avoir persisté le résultat nécessaire afin qu’une nouvelle tentative soit sûre.
Machine à états : rendez les transitions autorisées explicites
Une machine à états évite que la logique dépende de l’ordre accidentel d’arrivée. Le guide de format SMPP classe ENROUTE comme état intermédiaire et DELIVERED, EXPIRED, DELETED et UNDELIVERABLE comme états finaux. Il indique également qu’un message en nouvelle tentative peut rester ENROUTE puis se terminer plus tard en EXPIRED ou DELIVERED.
Représentez les états reçus sans les réécrire et définissez une couche d’état opérationnel calculé. Dans le modèle SMPP décrit, les états finaux ne progressent pas vers d’autres états. Par conséquent, un événement ultérieur qui semble contredire un état final déjà calculé ne doit pas le remplacer silencieusement : il doit être conservé et ouvrir une exception de réconciliation ou d’investigation.
Ne généralisez pas les codes d’erreur d’une plateforme à une autre. Les codes réseau ou SMSC peuvent être spécifiques à la passerelle ou à la plateforme. Stockez-les comme éléments de preuve d’origine et créez des classifications internes uniquement lorsque leurs règles sont documentées pour l’intégration concernée.
- État brut : la valeur reçue, sans réinterprétation destructive.
- État normalisé : une catégorie interne documentée, si l’intégration permet une correspondance fiable.
- État calculé : le résultat de l’application de règles de priorité et de transition sur les événements corrélés.
- Exception : conflit entre événements, événement final contradictoire, absence de corrélation ou format non reconnu.
- Règle prudente : un état final déjà matérialisé ne doit pas devenir un autre état final simplement parce qu’un callback ultérieur arrive.
Événements tardifs et dans le désordre : préservez les éléments de preuve et calculez avec prudence
Conservez au moins deux horodatages distincts : le moment où votre système a reçu le callback et le moment rapporté par le callback, s’il est fourni. Il peut également être nécessaire de conserver les dates de présentation et de finalisation incluses dans les formats de reçu. Ces marques ne sont pas interchangeables : l’une décrit l’observation locale et l’autre la donnée communiquée par la plateforme de messagerie.
Pour décider de l’état calculé, ne classez pas aveuglément selon l’instant d’arrivée. Appliquez une politique documentée qui tient compte du type d’état, de son caractère intermédiaire ou final et de la qualité de la corrélation. S’il n’existe pas de base fiable pour ordonner deux événements, n’inventez pas de séquence : conservez-les tous deux et marquez le conflit.
La sortie opérationnelle doit distinguer l’historique complet du résumé actuel. Un tableau de bord peut afficher un état calculé final tout en indiquant la présence d’événements répétés, tardifs ou conflictuels. Cette approche réduit la tentation de masquer des signaux qui deviennent ultérieurement essentiels au support, au rapprochement ou à l’analyse de qualité.
- Conservez l’ordre de réception local, les horodatages rapportés et le payload d’origine.
- Ne supprimez pas un événement en double : signalez-le comme répétition du même événement logique lorsque c’est le cas.
- Ne remplacez pas un état final par un autre état final incompatible sans règle contractuelle ou technique vérifiable pour cette intégration.
- Utilisez une file d’investigation pour les conflits et les messages non corrélés.
- Exposez dans l’audit l’événement qui étaye l’état calculé et les événements qui n’ont pas pu être appliqués.
Liste de vérification pour la production
Avant de connecter des callbacks DLR à des métriques, à la facturation, à des alertes ou à des décisions de campagne, testez l’intégration comme un système d’événements et non uniquement comme un appel HTTP réussi. La priorité est qu’une nouvelle tentative, un payload inattendu ou un reçu sans corrélation ne se transforme pas en une affirmation erronée de livraison.
BulkSMSMarket décrit la connectivité HTTP et SMPP, ainsi que des outils orientés vers la qualité et la cohérence des DLR. Dans toute connexion, confirmez la sémantique précise des champs, les formats et les règles de reçu de la contrepartie avant de les transformer en automatisations critiques.
- Testez la répétition du même callback et confirmez que l’état calculé et les compteurs ne changent pas à cause de cette répétition.
- Testez un événement intermédiaire arrivant après un état final et confirmez qu’il ne dégrade ni n’écrase le résultat final.
- Testez deux états finaux incompatibles et vérifiez que le second est préservé et marqué pour investigation.
- Testez les callbacks sans ID d’événement, avec un ID externe introuvable et avec des formats de date ou de payload non reconnus.
- Vérifiez que les messages non corrélés ne sont pas automatiquement attribués selon la destination ou le contenu.
- Vérifiez que les rapports distinguent l’acceptation, l’état DLR rapporté et toute vérification indépendante qui pourrait exister en dehors du DLR.
Questions fréquentes
Un callback DLR en double signifie-t-il que le SMS a été livré deux fois ?
Non. Il peut s’agir de la répétition d’une notification. Il doit être conservé comme réception observée, mais l’effet sur l’état calculé et les compteurs ne doit être appliqué qu’une seule fois à l’événement logique.
Puis-je corréler un DLR en utilisant uniquement le numéro de destination ?
Ce n’est pas recommandé. La destination peut se répéter pour plusieurs messages. La base principale de corrélation doit être l’ID externe attribué au message, tel que le message_id SMPP associé à receipted_message_id. La destination sert de validation auxiliaire.
DELIVERED confirme-t-il que le destinataire a lu le message ?
Non. DELIVERED est un état de livraison rapporté dans le flux de messagerie. Il ne constitue pas une confirmation indépendante de lecture humaine ni d’affichage du contenu sur le terminal.
Que faire si un état final arrive après un autre état final différent ?
Conservez les deux événements et marquez le conflit pour investigation. Ne remplacez pas silencieusement un état final par un autre état final incompatible uniquement selon l’ordre de réception du callback.
Est-il nécessaire de conserver le payload DLR d’origine ?
Oui. Les formats et les codes peuvent varier selon les plateformes. Le payload d’origine permet d’auditer le parseur, d’examiner les champs non normalisés et d’investiguer les écarts sans perdre les éléments de preuve reçus.
Sources consultées
- SMPP Protocol Specification v3.4, Issue 1.2SMPP Developers Forum
- SMPP Delivery Receipt FormatSMPP Developers Forum
- RFC 9110: HTTP SemanticsIETF / RFC Editor
- Recommendation ITU-T E.164: The international public telecommunication numbering planInternational Telecommunication Union