Vai al contenuto
EuroVPSEuroVPS

Ogni integrazione tra sistemi di ticket consegna due volte. Costruitela per questo.

Webhook che ritentano, partner che reinviano, reti che perdono risposte. Come costruire un bridge tra sistemi di ticket firmato, idempotente, senza loop e con permessi limitati, con codice che potete riutilizzare.

Di EuroVPS Engineering

5 min di lettura
Every ticket integration delivers twice. Build it for that.

Collegare il vostro sistema di ticket al portale di un partner sembra il lavoro di un pomeriggio: inviate un webhook quando qualcosa cambia, chiamate la loro API quando rispondono. La prima demo funziona sempre. I problemi iniziano alla terza settimana, quando un timeout spinge il mittente a ritentare, un ticket riceve la stessa risposta due volte e due sistemi cominciano a rispondersi a vicenda in loop alle tre di notte. Questo articolo parla delle quattro proprietà che lo impediscono, qualunque siano le piattaforme di ticketing alle due estremità.

Date per scontata la consegna at-least-once

Qualsiasi mittente che ritenta può consegnare lo stesso evento più di una volta. Non ha alternative: se il vostro server ha elaborato la richiesta ma la risposta si è persa sulla via del ritorno, il mittente vede un timeout e deve riprovare. «Exactly once» non esiste su una rete. Quello che esiste è la consegna at-least-once più un ricevente capace di vedere la stessa cosa due volte senza danni.

La prima regola di progettazione, quindi, è che ogni evento porti un identificativo stabile e univoco scelto dal mittente, e che il ricevente registri gli identificativi che ha già applicato.

CREATE TABLE applied_events (
  event_id    TEXT PRIMARY KEY,
  received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  ticket_ref  TEXT
);

A ogni consegna, provate prima a inserire l'ID. Se l'insert fallisce sulla primary key, quel lavoro lo avete già fatto: rispondete con successo e fermatevi. Rispondere con successo conta. Se rispondete con un errore, il mittente continuerà a ritentare qualcosa che avete già applicato.

L'ID dell'evento deve appartenere all'evento, non al tentativo di consegna. Una risposta sul ticket 4711 dovrebbe portare un ID derivato da quella risposta, per esempio l'ID della transazione nel sistema di origine, in modo che ogni suo ritentativo porti lo stesso valore.

Firmate ogni consegna, e controllate l'orologio

Firmate ogni consegna: un HMAC sul body grezzo con un segreto condiviso, inviato in un header, insieme a un timestamp, così una richiesta intercettata non potrà essere riprodotta la settimana dopo.

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)

Tre dettagli che quasi tutti sbagliano. Firmate i byte grezzi, non un oggetto JSON riserializzato, perché due librerie non serializzeranno lo stesso oggetto nello stesso modo. Confrontate con una funzione a tempo costante, non con ==. E tenete un segreto separato per ogni partner, così potete ruotarne uno senza toccare gli altri.

Spezzate i loop di proposito

Un bridge copia le risposte in entrambe le direzioni. Se il sistema A inoltra le sue risposte a B, e B inoltra le sue ad A, una risposta arrivata da B è, dal punto di vista di A, una nuova risposta sul ticket. Senza una protezione torna dritta indietro. Poi B fa lo stesso.

La soluzione è marcare l'origine e controllarla prima di inoltrare:

  • Quando applicate una risposta in ingresso, registrate che è arrivata dal partner, per esempio in un campo personalizzato o in un attributo della transazione.
  • Il vostro trigger in uscita salta ogni transazione marcata come proveniente dal partner.
  • Fate lo stesso sul lato del partner. Non affidatevi a una sola estremità.

Poi provatelo con un ticket vero: inviate una risposta da ciascun lato e verificate che ognuna compaia esattamente una volta dall'altra parte.

Date al partner una sola coda e nient'altro

La direzione in ingresso di solito significa che il partner chiama l'API del vostro sistema di ticket. Dategli un account dedicato, mai un token amministrativo condiviso, e concedete i diritti su una sola coda.

In Request Tracker questo significa un utente senza diritti globali e poi, solo sulla coda condivisa, solo i diritti di cui il bridge ha bisogno: tipicamente SeeQueue, ShowTicket, CreateTicket, ReplyToTicket e ModifyTicket. Lasciate fuori ShowTicketComments, così il partner non potrà mai leggere i commenti interni, e non concedete nulla su nessun'altra coda. Emettete un auth token per quell'utente e consegnatelo al partner.

Poi dimostrate il perimetro invece di fidarvi:

# 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

Un consiglio specifico per Request Tracker: se RT gira sotto Apache con mod_fcgid, l'header Authorization non viene passato all'applicazione a meno che non aggiungiate FcgidPassHeader Authorization al virtual host. Le richieste con token falliscono allora come non autenticate, anche se il token è valido. Vale la pena saperlo prima di perderci una serata.

Tenete i commenti fuori dal bridge

Inoltrate le risposte. Non inoltrate mai i commenti. I vostri ingegneri hanno bisogno di un posto dove scrivere «questo cliente l'ha già chiesto tre volte» senza che compaia nel portale di qualcun altro. Provatelo con un commento che contenga un marcatore evidente e cercatelo sul lato del partner.

Non scartate mai nulla in silenzio

Quando l'endpoint del partner è giù, le vostre consegne in uscita devono aspettare, non sparire:

  1. Ritentate con exponential backoff e jitter, per un tempo limitato.
  2. Dopo di che, parcheggiate l'evento in un dead-letter store insieme all'errore.
  3. Scansionate il dead-letter store a intervalli programmati e ritentate.
  4. Avvisate una persona se qualcosa resta parcheggiato più a lungo di quanto potete tollerare.
  5. Registrate ogni consegna con ID dell'evento, ticket, codice di stato e durata.

Fate la consegna in un worker, non dentro la richiesta stessa del sistema di ticket. Se il partner è lento, il vostro personale non deve aspettare che una pagina si salvi.

Altre due abitudini difensive. Verificate gli allegati con un checksum su entrambi i lati durante i test. «Sembra uguale» non è un test. E mettete un limite alla dimensione di ciò che accettate: un'immagine inline incollata a piena risoluzione dentro un ticket può pesare molte volte più del testo che la circonda. Ritagliatela o archiviatela a parte all'ingresso, invece di lasciare che un ticket pesante rallenti ogni pagina che lo mostra.

Una checklist per il vostro bridge

  • Ogni evento ha un ID scelto dal mittente, e il ricevente registra gli ID applicati.
  • Ogni richiesta è firmata sul body grezzo, con un timestamp e un segreto per partner.
  • Le modifiche provenienti dal partner sono marcate e non vengono mai rimandate indietro.
  • Il token del partner funziona su una coda e viene rifiutato su ogni altra, dimostrato con un test.
  • I commenti non passano mai. Le risposte passano sempre.
  • Esistono ritentativi, dead letter, scansioni e avvisi, e ogni consegna viene registrata.
  • Gli allegati sono verificati con checksum. I contenuti sovradimensionati vengono gestiti all'arrivo.

Costruiamo e gestiamo bridge di questo tipo tra Request Tracker e i portali dei partner. Se ne state progettando uno, o convivete con uno che non si comporta bene, saremo lieti di aiutarvi.

Parla con un ingegnere

Related

Correlati: Managed Request Tracker con partner bridges · Managed team chat su Mattermost

Pubblicato il 9 ottobre 2026.

Tutte le note dal campo