Chaque intégration de tickets livre deux fois. Construisez-la pour cela.
Des webhooks qui réessaient, des partenaires qui renvoient, des réseaux qui perdent des réponses. Comment construire un pont de tickets signé, idempotent, sans boucles et à périmètre limité, avec du code que vous pouvez réutiliser.

Connecter votre système de ticketing au portail d'un partenaire ressemble à un après-midi de travail : vous envoyez un webhook quand quelque chose change, vous appelez leur API quand ils répondent. La première démo fonctionne toujours. Les ennuis commencent la troisième semaine, quand un timeout fait réessayer l'expéditeur, qu'un ticket reçoit deux fois la même réponse, et que deux systèmes commencent à se répondre en boucle à trois heures du matin. Cet article porte sur les quatre propriétés qui empêchent cela, quelles que soient les plateformes de ticketing à chaque bout.
Partez du principe d'une livraison at-least-once
Tout expéditeur qui réessaie peut livrer le même événement plus d'une fois. Il n'a pas le choix : si votre serveur a traité la requête mais que la réponse s'est perdue au retour, l'expéditeur voit un timeout et doit réessayer. Le « exactly once » n'existe pas sur un réseau. Ce qui existe, c'est une livraison at-least-once plus un récepteur qui peut voir la même chose deux fois sans danger.
La première règle de conception est donc que chaque événement porte un identifiant stable et unique choisi par l'expéditeur, et que le récepteur enregistre les identifiants qu'il a déjà appliqués.
CREATE TABLE applied_events (
event_id TEXT PRIMARY KEY,
received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
ticket_ref TEXT
);
À chaque livraison, essayez d'abord d'insérer l'ID. Si l'insert échoue sur la clé primaire, vous avez déjà fait ce travail : répondez succès et arrêtez-vous. Répondre succès compte. Si vous répondez par une erreur, l'expéditeur continuera de réessayer quelque chose que vous avez déjà appliqué.
L'ID de l'événement doit appartenir à l'événement, pas à la tentative de livraison. Une réponse sur le ticket 4711 doit porter un ID dérivé de cette réponse, par exemple l'ID de transaction dans le système source, pour que chaque nouvelle tentative porte la même valeur.
Signez chaque livraison, et vérifiez l'horloge
Signez chaque livraison : un HMAC sur le corps brut avec un secret partagé, envoyé dans un en-tête, accompagné d'un horodatage pour qu'une requête capturée ne puisse pas être rejouée la semaine suivante.
import hmac, hashlib, time
SECRET = load_secret() # never in the code repository
MAX_SKEW = 300 # seconds
def verify(raw_body: bytes, ts_header: str, sig_header: str) -> bool:
ts = int(ts_header)
if abs(time.time()-ts) > MAX_SKEW:
return False
signed = f"{ts}.".encode() + raw_body
expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig_header)
Trois détails que la plupart des gens ratent. Signez les octets bruts, pas un objet JSON resérialisé, parce que deux bibliothèques ne sérialiseront pas le même objet à l'identique. Comparez avec une fonction à temps constant, pas avec ==. Et gardez un secret distinct par partenaire, pour pouvoir en faire tourner un sans toucher aux autres.
Cassez les boucles délibérément
Un pont copie les réponses dans les deux sens. Si le système A transmet ses réponses à B, et que B transmet les siennes à A, alors une réponse arrivée de B est, du point de vue de A, une nouvelle réponse sur le ticket. Sans garde-fou, elle repart aussitôt. Puis B fait de même.
La solution consiste à marquer l'origine et à la vérifier avant de transmettre :
- Quand vous appliquez une réponse entrante, enregistrez qu'elle vient du partenaire, par exemple dans un champ personnalisé ou un attribut de la transaction.
- Votre déclencheur sortant ignore toute transaction marquée comme provenant du partenaire.
- Faites de même côté partenaire. Ne comptez pas sur un seul bout.
Puis testez-le avec un vrai ticket : envoyez une réponse depuis chaque côté et vérifiez que chacune apparaît exactement une fois de l'autre côté.
Donnez au partenaire une file et rien d'autre
Le sens entrant signifie généralement que le partenaire appelle l'API de votre système de ticketing. Donnez-lui un compte dédié, jamais un token administrateur partagé, et n'accordez des droits que sur une seule file.
Dans Request Tracker, cela veut dire un utilisateur sans droits globaux, puis, sur la file partagée seulement, les droits dont le pont a besoin : typiquement SeeQueue, ShowTicket, CreateTicket, ReplyToTicket et ModifyTicket. Laissez de côté ShowTicketComments, pour que le partenaire ne puisse jamais lire les commentaires internes, et n'accordez rien sur aucune autre file. Émettez un auth token pour cet utilisateur et remettez-le au partenaire.
Puis prouvez le périmètre plutôt que de lui faire confiance :
# Should succeed: the shared queue
curl -s -o /dev/null -w '%{http_code}\n' \
-H "Authorization: token $PARTNER_TOKEN" \
https://rt.example.com/REST/2.0/queue/Partner-Shared
# Should be refused: any other queue
curl -s -o /dev/null -w '%{http_code}\n' \
-H "Authorization: token $PARTNER_TOKEN" \
https://rt.example.com/REST/2.0/queue/General
Une astuce propre à Request Tracker : si RT tourne sous Apache avec mod_fcgid, l'en-tête Authorization n'est pas transmis à l'application à moins d'ajouter FcgidPassHeader Authorization au virtual host. Les requêtes par token échouent alors comme non authentifiées, bien que le token soit valide. Cela vaut la peine de le savoir avant d'y passer une soirée.
Gardez les commentaires hors du pont
Transmettez les réponses. Ne transmettez jamais les commentaires. Vos ingénieurs ont besoin d'un endroit pour dire « ce client a déjà posé la question trois fois » sans que cela apparaisse dans le portail de quelqu'un d'autre. Testez-le avec un commentaire contenant un marqueur évident et cherchez-le côté partenaire.
Ne perdez jamais rien en silence
Quand le endpoint du partenaire est hors service, vos livraisons sortantes doivent attendre, pas disparaître :
- Réessayez avec un backoff exponentiel et du jitter, pendant une durée bornée.
- Passé ce délai, garez l'événement dans un dead-letter store avec l'erreur.
- Balayez le dead-letter store à intervalle régulier et réessayez.
- Alertez un humain si quelque chose reste garé plus longtemps que vous ne pouvez le tolérer.
- Journalisez chaque livraison avec l'ID d'événement, le ticket, le code de statut et la durée.
Faites la livraison dans un worker, pas à l'intérieur de la requête du système de ticketing lui-même. Si le partenaire est lent, votre personnel ne doit pas attendre qu'une page s'enregistre.
Deux autres habitudes défensives. Vérifiez les pièces jointes par checksum des deux côtés pendant les tests. « Ça a l'air pareil » n'est pas un test. Et plafonnez la taille de ce que vous acceptez : une image inline collée en pleine résolution dans un ticket peut peser bien plus que le texte qui l'entoure. Réduisez-la ou stockez-la séparément à l'entrée, plutôt que de laisser un ticket lourd ralentir chaque page qui l'affiche.
Une checklist pour votre propre pont
- Chaque événement a un ID choisi par l'expéditeur, et le récepteur enregistre les ID appliqués.
- Chaque requête est signée sur le corps brut, avec un horodatage et un secret par partenaire.
- Les changements provenant du partenaire sont marqués et ne sont jamais renvoyés.
- Le token du partenaire fonctionne sur une file et est refusé sur toutes les autres, preuve par le test.
- Les commentaires ne traversent jamais. Les réponses traversent toujours.
- Les réessais, les dead letters, les balayages et les alertes existent, et chaque livraison est journalisée.
- Les pièces jointes sont vérifiées par checksum. Le contenu surdimensionné est traité à l'arrivée.
Nous construisons et exploitons des ponts de ce type entre Request Tracker et des portails partenaires. Si vous en planifiez un, ou si vous vivez avec un qui se comporte mal, nous serons heureux de vous aider.
Related
Sur le même sujet : Managed Request Tracker avec partner bridges · Managed team chat sur Mattermost
Publié le 9 octobre 2026.
Toutes les notes de terrain