Aller au contenu

Déclencheurs web (webhooks)

Un déclencheur web lance une mission lorsqu’un service externe envoie un événement à Diadems. Il convient par exemple à une nouvelle commande, un formulaire reçu, une alerte ou un changement de statut.

Le service externe appelle uniquement l’URL publique fournie par Diadems. Il n’accède jamais directement au serveur interne, au port de la capsule ou à une clé API générale Diadems.

  1. Ouvrez Agent → Tâches → Déclencheurs événementiels.
  2. Donnez un nom stable au déclencheur, par exemple Nouvelle commande.
  3. Rédigez la consigne que l’agent devra exécuter.
  4. Si nécessaire, indiquez les types d’événements acceptés, séparés par des virgules.
  5. Choisissez où livrer le résultat : dans le portail, sur Telegram ou vers votre application avec un callback HTTPS.
  6. Enregistrez.

Diadems affiche une URL HTTPS et un secret propres à ce déclencheur. Le secret sert uniquement à signer les requêtes de cette route.

Contrat recommandé pour une intégration personnalisée

Section intitulée « Contrat recommandé pour une intégration personnalisée »

Une intégration générique doit utiliser la signature HMAC V2, qui lie la requête à un horodatage et empêche la réutilisation tardive d’une requête interceptée.

  1. Sérialisez le JSON une seule fois et conservez exactement cette chaîne comme corps HTTP.
  2. Produisez un timestamp Unix en secondes, par exemple 1785920400.
  3. Construisez les octets à signer : <timestamp>.<corps JSON brut>.
  4. Calculez leur HMAC-SHA256 avec le secret du déclencheur.
  5. Encodez le résultat en hexadécimal minuscule.
  6. Envoyez le corps sans le sérialiser ni le modifier une seconde fois.
POST <URL affichée par Diadems>
Content-Type: application/json
X-Webhook-Timestamp: <timestamp Unix en secondes>
X-Webhook-Signature-V2: <HMAC-SHA256 hexadécimal>
X-Request-ID: <identifiant unique et stable de l’événement>
<corps JSON brut>

La signature est donc :

hex(HMAC-SHA256(secret, UTF8(timestamp + "." + corps_json_brut)))

L’horloge du service émetteur doit être synchronisée. Diadems refuse un timestamp éloigné de plus de 5 minutes de l’heure du serveur.

X-Request-ID doit identifier l’événement métier, pas la tentative HTTP. Rendez-le unique pour tout l’agent en préfixant la source et le type, par exemple boutique:order.created:1234. En cas de nouvelle tentative, réutilisez le même identifiant : la capsule reconnaît durablement une livraison déjà acceptée, y compris après un redémarrage, au lieu de relancer l’agent.

La variable $body utilisée pour calculer la signature est exactement celle transmise par cURL.

<?php
$url = getenv('DIADEMS_WEBHOOK_URL');
$secret = getenv('DIADEMS_WEBHOOK_SECRET');
$eventId = 'boutique:order.created:' . $order->id;
$payload = [
'event_type' => 'order.created',
'event_id' => $eventId,
'occurred_at' => gmdate(DATE_ATOM),
'data' => [
'order_id' => (string) $order->id,
'customer_id' => (string) $order->customer_id,
],
];
$body = json_encode(
$payload,
JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);
$timestamp = (string) time();
$signature = hash_hmac('sha256', $timestamp . '.' . $body, $secret);
$request = curl_init($url);
curl_setopt_array($request, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Webhook-Timestamp: ' . $timestamp,
'X-Webhook-Signature-V2: ' . $signature,
'X-Request-ID: ' . $eventId,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$responseBody = curl_exec($request);
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
curl_close($request);
import { createHmac } from "node:crypto";
const eventId = "boutique:order.created:1234";
const body = JSON.stringify({
event_type: "order.created",
event_id: eventId,
occurred_at: new Date().toISOString(),
data: { order_id: "1234" },
});
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = createHmac("sha256", process.env.DIADEMS_WEBHOOK_SECRET)
.update(`${timestamp}.`, "utf8")
.update(body, "utf8")
.digest("hex");
const response = await fetch(process.env.DIADEMS_WEBHOOK_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Webhook-Timestamp": timestamp,
"X-Webhook-Signature-V2": signature,
"X-Request-ID": eventId,
},
body,
});

Diadems accepte tout objet JSON, mais un format stable facilite le filtrage, la lecture et les évolutions :

{
"event_type": "order.created",
"event_id": "order-1234",
"occurred_at": "2026-08-05T10:15:00Z",
"data": {
"order_id": "1234"
}
}
  • event_type détermine le type d’événement utilisé par les filtres de la route ;
  • event_id identifie l’événement métier dans le contenu ;
  • occurred_at indique quand l’événement s’est réellement produit ;
  • data contient seulement les données nécessaires à la mission.

Évitez d’envoyer un mot de passe, une clé API ou une donnée inutile dans le corps. Une signature authentifie l’émetteur, mais le contenu reçu reste une entrée non fiable pour l’agent.

Statut Signification Action de l’émetteur
202 Événement authentifié et écrit dans la file durable de la capsule Ne pas renvoyer
200 Événement ignoré ou doublon déjà accepté Ne pas renvoyer
400 Corps illisible ou mal formé Corriger la requête
401 Signature absente ou incorrecte Corriger le secret ou le calcul
403 Route désactivée ou sans secret valide Corriger la configuration
404 Agent ou route inconnu Corriger l’URL
413 Corps supérieur à 1 Mio Réduire le corps
429 Limite temporaire atteinte Réessayer avec attente exponentielle
502 ou 503 Capsule momentanément indisponible Réessayer avec attente exponentielle

Pour 429, 502 et 503, conservez le même X-Request-ID. Si la nouvelle tentative intervient plus de cinq minutes après la première, générez un nouveau timestamp et recalculez la signature sur le même corps.

Après validation de la signature, Hermes enregistre l’événement dans une file SQLite située sur le volume privé de la capsule. La réponse 202 n’est envoyée qu’après la validation de cette écriture. Le corps, la consigne rendue et les informations de livraison ne sont ni enregistrés dans PostgreSQL Diadems, ni renvoyés au plan de contrôle.

La capsule traite les événements avec une concurrence bornée. Un échec est réessayé automatiquement jusqu’à trois tentatives, avec des attentes de 30 secondes puis 5 minutes. Après le troisième échec, la livraison passe dans l’état Échec à vérifier et peut être relancée manuellement depuis la fiche du déclencheur. Le portail affiche uniquement l’identifiant, le type, l’état, les dates et le nombre de tentatives ; il ne lit jamais le contenu de l’événement.

Une fois la mission terminée, le contenu sensible de la ligne de file est effacé. La trace technique de réussite est conservée sept jours pour reconnaître les reprises tardives. Une livraison en échec conserve son contenu dans la capsule pendant quatorze jours afin de permettre une relance, puis elle est supprimée.

Le webhook est asynchrone. Une réponse 202 Accepted signifie que Hermes a authentifié et enregistré durablement la mission dans la capsule ; elle ne contient pas le résultat final de l’agent. Ne gardez jamais la requête entrante ouverte en attendant la fin du travail.

La fin d’un webhook reste disponible dans sa session et dans le suivi technique. Elle n’apparaît dans votre cloche et sur vos appareils que si vous activez Me notifier dans Diadems, Windows et mobile pour ce déclencheur. Ce choix est personnel : il ne crée aucune notification chez les autres membres de l’équipe.

Choisissez Vers mon application dans Livraison du résultat pour recevoir automatiquement la réponse finale sur votre API :

  1. indiquez une URL HTTPS publique sans identifiants, paramètres ni fragment ;
  2. choisissez un jeton Bearer ou une signature HMAC ;
  3. saisissez le jeton ou le secret partagé ;
  4. adaptez si nécessaire les noms content et event_id ;
  5. ajoutez les champs scalaires de l’événement à renvoyer, par exemple data.order.id vers order_id ;
  6. créez le déclencheur puis utilisez Tester le retour.

Le secret est écrit directement dans le magasin dotenv privé de la capsule. Il n’est ni conservé dans PostgreSQL Diadems, ni réaffiché par l’API. Une rotation est possible depuis Configurer le retour : laissez le champ vide pour garder le secret existant, ou saisissez une nouvelle valeur pour le remplacer.

Le corps sortant contient uniquement les champs explicitement déclarés, la réponse finale et l’identifiant stable :

{
"order_id": "1234",
"content": "Analyse terminée…",
"event_id": "boutique:order.created:1234"
}

Avec Jeton Bearer, la capsule envoie :

Authorization: Bearer <jeton privé>
X-Request-ID: <identifiant stable>

Avec Signature HMAC, elle signe exactement le corps JSON avec la même construction V2 que le webhook entrant :

X-Webhook-Timestamp: <timestamp Unix>
X-Webhook-Signature-V2: hex(HMAC-SHA256(secret, timestamp + "." + corps brut))
X-Request-ID: <identifiant stable>

Votre endpoint doit répondre avec un statut 2xx. Une redirection ou un autre statut est considéré comme un échec. Diadems ne marque la livraison comme terminée qu’après ce 2xx ; le callback bénéficie donc des trois tentatives, du backoff et de la relance manuelle de la file capsule. La garantie reste « au moins une fois » : utilisez event_id ou X-Request-ID comme contrainte unique afin qu’une reprise n’applique pas deux fois la même mutation.

Pour empêcher un callback de devenir un accès au réseau interne, la capsule refuse les hôtes locaux, privés, réservés ou non publics, épingle l’adresse DNS publique pendant la connexion TLS et ne suit aucune redirection.

Les fournisseurs pris en charge peuvent conserver leur signature native :

  • GitHub : X-Hub-Signature-256 au format sha256=<empreinte hexadécimale> ;
  • GitLab : X-Gitlab-Token contenant le secret exact ;
  • Svix : svix-id, svix-timestamp et svix-signature.

Pour une intégration développée sur mesure, utilisez la signature générique V2 décrite plus haut. L’ancien en-tête X-Webhook-Signature, signé uniquement sur le corps, reste accepté par Hermes pour compatibilité mais ne protège pas contre le rejeu temporel et ne doit pas être utilisé pour une nouvelle intégration.

Le bouton Tester l’événement envoie un événement signé de contrôle et lance réellement l’agent. Vérifiez ensuite le résultat dans Sessions ou sur Telegram. La fiche du déclencheur affiche également les dernières livraisons et leur état : en attente, en cours, nouvelle tentative, terminée ou en échec.

Supprimez un déclencheur qui n’est plus utilisé. Son URL et son secret cessent alors d’être valides. Pour remplacer un secret possiblement compromis, supprimez le déclencheur, recréez-le puis mettez à jour le gestionnaire de secrets du service émetteur.