Aller au contenu

Webhooks sortants

Avec un webhook sortant, exoCortex envoie une requête POST à votre système chaque fois que se produit un événement auquel vous êtes abonné : un ticket créé, un changement de statut, un SLA sur le point d’expirer. Votre système réagit ainsi immédiatement, sans interroger l’API à intervalles réguliers.

  1. Dans exoCortex Core, ouvrez Integrations → Webhooks. Un rôle administratif est nécessaire.
  2. Indiquez l’URL qui recevra les événements. Elle doit commencer par https://.
  3. Choisissez les événements. * signifie tous les événements.
  4. Choisissez le format :
    • json : le format standard d’exoCortex, décrit sur cette page ;
    • datadog, pagerduty, slack-block : le format natif de ces services, à utiliser lorsque l’URL est la leur.
  5. Si votre passerelle l’exige, ajoutez des en-têtes fixes, par exemple un en-tête d’authentification.
  6. Enregistrez. Si vous n’avez pas indiqué de secret de signature, exoCortex en génère un (whsec_…) et ne l’affiche qu’une seule fois : enregistrez-le, il vous sert à vérifier les signatures.

Avec l’action de test, vous envoyez à l’abonnement l’événement webhook.test et vous vérifiez que votre système le reçoit.

Événement Quand il arrive
ticket.created Un ticket a été créé
ticket.comment_added Un commentaire a été ajouté
ticket.field_changed Un champ a changé
ticket.assignee_changed L’assigné a changé
ticket.status_changed Le statut a changé
ticket.priority_changed La priorité a changé
ticket.sla_warning Un SLA est sur le point d’expirer
ticket.sla_breach Un SLA a expiré
ticket.resolved Le ticket a été résolu
ticket.closed Le ticket a été fermé
ticket.reopened Le ticket a été rouvert
ticket.link_created Un lien entre tickets a été créé
ticket.link_deleted Un lien entre tickets a été supprimé
routing.no_match Aucune règle de routage n’a trouvé de file pour le ticket
routing.manual_routed Un opérateur a acheminé le ticket manuellement
webhook.test Vous avez envoyé un événement de test à l’abonnement

En plus de ceux-ci, vous pouvez vous abonner par leur nom aux événements des règles de notification de votre tenant, par exemple approval_requested.

exoCortex peut ajouter de nouveaux types d’événements : votre système doit ignorer ceux qu’il ne connaît pas.

Chaque événement au format json a la même structure :

{
"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" }
}
}
  • id identifie l’événement et il est identique pour tous les abonnements qui le reçoivent.
  • actor est l’utilisateur qui a déclenché l’événement, ou system.
  • changes contient les détails de l’événement : par exemple l’ancien et le nouveau statut, ou le texte du commentaire.

Jusqu’au 30 juin 2027, le corps contient aussi les champs dépréciés event, timestamp et source, ainsi qu’une copie des champs de data au premier niveau. Un nouveau système doit lire uniquement les champs décrits ci-dessus.

En-tête Exemple Signification
X-Exo-Timestamp 1793491200 Moment de la signature, en secondes Unix
X-Exo-Signature v1=5257a869… Signature de l’événement
X-Exo-Event ticket.created Type d’événement, identique à type
X-Exo-Delivery dlv_0b9d… Identifiant unique de la tentative de livraison

Avant de faire confiance à un événement, vérifiez sa signature :

  1. Lisez le corps tel qu’il est arrivé, sans interpréter ni réécrire le JSON : les octets changeraient.
  2. Rejetez la requête si X-Exo-Timestamp est absent ou s’écarte de plus de 300 secondes de votre horloge. Vous bloquez ainsi les requêtes rejouées. Gardez votre horloge synchronisée par NTP.
  3. Calculez le HMAC-SHA256 de timestamp + "." + body avec le secret de l’abonnement, en hexadécimal minuscule.
  4. Comparez le résultat, en temps constant, avec chaque valeur v1= de X-Exo-Signature. Pendant la rotation du secret, l’en-tête peut en contenir plusieurs, séparées par des virgules.

Exemple en 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);
});
}

Exemple en 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’en-tête X-NH-Signature, qui signe uniquement le corps sans horodatage, est déprécié et sera supprimé le 30 juin 2027.

  • Répondez avec n’importe quel code 2xx dans un délai de 5 secondes. Si vous devez effectuer un traitement long, mettez-le en file d’attente et répondez immédiatement.
  • La livraison est garantie au moins une fois : le même événement peut arriver plusieurs fois. Utilisez id ou X-Exo-Delivery pour reconnaître les doublons, par exemple en conservant les identifiants vus au cours des 5 dernières minutes.
  • Les événements peuvent arriver dans un ordre différent de celui dans lequel ils se sont produits : triez-les avec created_at.