Webhook in uscita
Con un webhook in uscita exoCortex invia una richiesta POST al tuo sistema ogni volta che succede un evento a cui ti sei iscritto: un ticket creato, un cambio di stato, uno SLA in scadenza. Così il tuo sistema reagisce subito, senza interrogare l’API a intervalli.
Creare una sottoscrizione
Sezione intitolata “Creare una sottoscrizione”- In exoCortex Core apri Integrations → Webhooks. Serve un ruolo amministrativo.
- Indica l’URL che riceverà gli eventi. Deve essere
https://. - Scegli gli eventi.
*significa tutti gli eventi. - Scegli il formato:
json: il formato standard di exoCortex, descritto in questa pagina;datadog,pagerduty,slack-block: il formato nativo di quei servizi, da usare quando l’URL è il loro.
- Se il tuo gateway lo richiede, aggiungi intestazioni fisse, per esempio un’intestazione di autenticazione.
- Salva. Se non hai indicato un segreto di firma, exoCortex ne genera uno (
whsec_…) e lo mostra una sola volta: salvalo, ti serve per verificare le firme.
Con l’azione di test invii alla sottoscrizione l’evento webhook.test e controlli che il tuo sistema lo riceva.
| Evento | Quando arriva |
|---|---|
ticket.created |
È stato creato un ticket |
ticket.comment_added |
È stato aggiunto un commento |
ticket.field_changed |
È cambiato un campo |
ticket.assignee_changed |
È cambiato l’assegnatario |
ticket.status_changed |
È cambiato lo stato |
ticket.priority_changed |
È cambiata la priorità |
ticket.sla_warning |
Uno SLA sta per scadere |
ticket.sla_breach |
Uno SLA è scaduto |
ticket.resolved |
Il ticket è stato risolto |
ticket.closed |
Il ticket è stato chiuso |
ticket.reopened |
Il ticket è stato riaperto |
ticket.link_created |
È stato creato un collegamento tra ticket |
ticket.link_deleted |
È stato rimosso un collegamento tra ticket |
routing.no_match |
Nessuna regola di routing ha trovato una coda per il ticket |
routing.manual_routed |
Un operatore ha instradato il ticket a mano |
webhook.test |
Hai inviato un evento di prova alla sottoscrizione |
Oltre a questi puoi iscriverti per nome agli eventi delle regole di notifica del tuo tenant, per esempio approval_requested.
exoCortex può aggiungere nuovi tipi di evento: il tuo sistema deve ignorare quelli che non conosce.
Formato dell’evento
Sezione intitolata “Formato dell’evento”Ogni evento in formato json ha la stessa struttura:
{ "id": "evt_8a1f4c2e9b7d4e3f8a6b5c4d3e2f1a00", "type": "ticket.status_changed", "created_at": "2026-11-01T09:31:47.120Z", "api_version": "1", "tenant_id": "ACME", "data": { "ticket": { "key": "ACME-1042", "summary": "Payment gateway returns HTTP 502", "status": "In Progress", "priority": "High", "issuetype": "IT-INC", "assignee": "jane.doe", "created": "2026-11-01T09:14:05.000Z", "updated": "2026-11-01T09:31:47.000Z" }, "actor": "jane.doe", "changes": { "from": "Open", "to": "In Progress" } }}ididentifica l’evento ed è lo stesso per tutte le sottoscrizioni che lo ricevono.actorè l’utente che ha causato l’evento, oppuresystem.changescontiene i dettagli dell’evento: per esempio stato precedente e nuovo, o il testo del commento.
Fino al 30 giugno 2027 il corpo contiene anche i campi deprecati event, timestamp e source e una copia dei campi di data al primo livello. Un nuovo sistema deve leggere solo i campi descritti sopra.
Intestazioni
Sezione intitolata “Intestazioni”| Intestazione | Esempio | Significato |
|---|---|---|
X-Exo-Timestamp |
1793491200 |
Momento della firma, in secondi Unix |
X-Exo-Signature |
v1=5257a869… |
Firma dell’evento |
X-Exo-Event |
ticket.created |
Tipo di evento, uguale a type |
X-Exo-Delivery |
dlv_0b9d… |
Identificativo univoco del tentativo di consegna |
Verificare la firma
Sezione intitolata “Verificare la firma”Prima di fidarti di un evento, verifica la firma:
- Leggi il corpo così come è arrivato, senza interpretare e riscrivere il JSON: cambierebbero i byte.
- Scarta la richiesta se
X-Exo-Timestampmanca o si discosta di più di 300 secondi dal tuo orologio. Così blocchi le richieste ripetute. Tieni l’orologio sincronizzato con NTP. - Calcola l’HMAC-SHA256 di
timestamp + "." + bodycon il segreto della sottoscrizione, in esadecimale minuscolo. - Confronta il risultato, in tempo costante, con ogni valore
v1=diX-Exo-Signature. Durante la rotazione del segreto l’intestazione può contenerne più di uno, separati da virgole.
Esempio in Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyExoWebhook(rawBody, headers, secret, toleranceSec = 300) { const ts = headers['x-exo-timestamp']; const sigHeader = headers['x-exo-signature'] || ''; if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false; const expected = Buffer.from(createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')); return sigHeader .split(',') .map((s) => s.trim()) .filter((s) => s.startsWith('v1=')) .some((s) => { const got = Buffer.from(s.slice(3)); return got.length === expected.length && timingSafeEqual(got, expected); });}Esempio in Python:
import hashlib, hmac, time
def verify_exo_webhook(raw_body: bytes, headers, secret: str, tolerance_sec: int = 300) -> bool: ts = headers.get('X-Exo-Timestamp') if not ts or abs(time.time() - int(ts)) > tolerance_sec: return False expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest() sigs = [s.strip()[3:] for s in headers.get('X-Exo-Signature', '').split(',') if s.strip().startswith('v1=')] return any(hmac.compare_digest(s, expected) for s in sigs)L’intestazione X-NH-Signature, che firma solo il corpo senza timestamp, è deprecata e sarà rimossa il 30 giugno 2027.
Rispondere e gestire i duplicati
Sezione intitolata “Rispondere e gestire i duplicati”- Rispondi con un qualsiasi codice
2xxentro 5 secondi. Se devi fare un lavoro lungo, mettilo in coda e rispondi subito. - La consegna è garantita almeno una volta: lo stesso evento può arrivare più di una volta. Usa
idoX-Exo-Deliveryper riconoscere i duplicati, per esempio conservando gli identificativi visti negli ultimi 5 minuti. - Gli eventi possono arrivare in ordine diverso da quello in cui sono avvenuti: ordinali con
created_at.