Un webhook est une adresse de votre système que Hermetry appelle lorsqu’un événement se produit. Il sert à alimenter un système de tickets, un ERP, un bus de messages ou votre propre service. Les webhooks sont gérés par les administrateurs dans Webhooks (dans la partie Developer du menu). L’envoi de webhooks nécessite une offre qui les inclut.
Créer un webhook
- Ouvrez Webhooks et sélectionnez New Webhook.
- Saisissez un Name et, facultativement, une Description (« À quoi sert ce webhook ? »).
- Saisissez l’URL. Elle doit être en HTTPS : « Webhook URL must be a valid HTTPS URL. »
- Sous Events, choisissez au moins un événement. La liste n’affiche que les événements des modules utilisés par votre organisation.
- Facultativement :
- Custom headers (optional) : jusqu’à 20 en-têtes envoyés à chaque appel, par exemple un jeton d’autorisation que votre récepteur vérifie.
Content-Type,User-Agent,Host,Content-LengthetX-Webhook-*ne peuvent pas être définis. Les valeurs tiennent sur une ligne, 1,000 caractères au maximum. - Event filters (optional, advanced) : ne livrer un événement que lorsque son payload correspond (voir ci-dessous).
- Tags (optional, comma-separated) : étiquettes pour organiser vos webhooks, 20 au maximum.
- Rate limit (optional, deliveries/minute) : vide signifie illimité.
- Custom headers (optional) : jusqu’à 20 en-têtes envoyés à chaque appel, par exemple un jeton d’autorisation que votre récepteur vérifie.
- Enregistrez. Copiez immédiatement le Signing secret : il n’est affiché qu’une seule fois.
Tous les événements des modules IoT sont listés avec des exemples de payload dans la Webhook event reference.
Filtres d’événements
Un filtre d’événement est un objet JSON qui associe un événement aux champs du payload que celui-ci doit respecter. Une livraison est ignorée lorsque le payload ne correspond pas. Les événements sans filtre sont toujours livrés.
{
"alarm.created": { "severity": "CRITICAL" },
"device.disconnected": { "profileId": "6f1c0a2e-0000-4000-8000-000000000001" }
}
Chaque chemin désigne un champ de data de l’événement et peut être pointé, par exemple originator.type. Chaque champ indiqué doit être égal à sa valeur (nombres et textes sont comparés comme du texte, donc "2" correspond à 2). 50 événements au maximum peuvent avoir un filtre.
À quoi ressemble une livraison
Hermetry envoie un POST avec ces en-têtes :
| En-tête | Valeur |
|---|---|
Content-Type | application/json (format par défaut) ou application/cloudevents+json; charset=utf-8 |
User-Agent | Hermetry-Webhooks/1.0 |
X-Webhook-Event | Le nom de l’événement, par exemple device.disconnected |
X-Webhook-Delivery | L’identifiant de la livraison. Identique à chaque nouvelle tentative et à chaque renvoi d’une même livraison. |
X-Webhook-Timestamp | Secondes Unix au moment où cette tentative a été signée |
X-Webhook-Signature-V2 | sha256= + HMAC-SHA256 de "<timestamp>.<body>" avec votre secret. Utilisez celui-ci. |
X-Webhook-Signature | HMAC-SHA256 du seul corps. Ancienne forme, conservée pendant une période de transition. |
X-Webhook-Signature-Prev, X-Webhook-Signature-V2-Prev | Les mêmes, avec le secret précédent, pendant une rotation de secret |
X-Webhook-Manual | true sur un événement envoyé manuellement par un administrateur |
X-Idempotency-Key | Sur les messages qu’un flux envoie via son bloc webhook |
Avec le format par défaut, le corps est une enveloppe :
{
"webhookId": "…",
"tenantId": "…",
"event": "device.disconnected",
"createdAt": "2026-11-03T12:00:30.000Z",
"data": { "deviceId": "…", "name": "router-17", "reason": "timeout", "…": "…" }
}
Un événement envoyé manuellement par un administrateur porte aussi "manual": true. Répondez par un statut 2xx quelconque dans le délai imparti. Les 4 premiers Ko du corps de votre réponse sont conservés dans le journal de livraison.
Format CloudEvents
Un point de terminaison peut recevoir à la place des CloudEvents 1.0 en mode structuré. Le formulaire de webhook ne propose pas encore de choix de format : définissez "format": "cloudevents" via l’API (POST ou PATCH /api/webhooks/..., voir ci-dessous). "json" est la valeur par défaut.
{
"specversion": "1.0",
"id": "8d2c…",
"source": "urn:hermetry:tenant:<tenantId>",
"type": "com.hermetry.device.connected",
"datacontenttype": "application/json",
"time": "2026-11-03T10:00:30.000Z",
"data": { "deviceId": "…", "name": "router-17", "transport": "mqtts", "…": "…" },
"tenantid": "<tenantId>",
"webhookid": "<webhookId>"
}
data contient exactement ce que le format par défaut contient sous data. id est identique à chaque nouvelle tentative et à chaque renvoi ; dédoublonnez donc sur source et id. Les en-têtes de signature sont les mêmes dans les deux formats. Les modes batch et binaire ne sont pas proposés.
Vérifier la signature
Vérifiez sur le corps brut, avant de l’analyser, et rejetez les horodatages anciens.
import crypto from 'crypto';
function verifyWebhook(body: string, timestamp: string, signatureV2: string, secret: string): boolean {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex');
// Reject deliveries older than 5 minutes.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const a = Buffer.from(expected);
const b = Buffer.from(signatureV2);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Pendant une rotation de secret, acceptez un appel lorsque X-Webhook-Signature-V2 ou X-Webhook-Signature-V2-Prev correspond.
Livraison, nouvelles tentatives et lettres mortes
| Paramètre | Valeur par défaut | Plage |
|---|---|---|
| Request Timeout (ms) | 15,000 | 1,000 à 120,000 |
| Max Delivery Attempts (première tentative incluse) | 3 | 1 à 10 |
| Retry Backoff (ms) : l’attente avant la nouvelle tentative 1, 2, … | 60000,300000,900000 | 1 à 9 valeurs, chacune de 1,000 à 86,400,000 ; au-delà de la liste, la dernière valeur |
| Circuit Breaker Threshold : nombre d’échecs consécutifs avant la désactivation du point de terminaison | 10 | 1 à 100,000 |
| Default Rate Limit (per minute) : pour les points de terminaison qui n’en ont pas | illimité | vide ou 0 = illimité |
Définissez-les dans Webhooks > paramètres (le bouton en forme d’engrenage). Une livraison est soit PENDING, SUCCESS, FAILED (sera relancée) soit DEAD_LETTERED (toutes les tentatives utilisées).
- Chaque livraison est journalisée avec son statut, ses tentatives, le code de réponse, l’erreur, la durée et l’heure de la prochaine tentative.
- Un point de terminaison au-delà de sa limite de débit reçoit ses livraisons environ 60 secondes plus tard ; rien n’est perdu.
- Après le seuil d’échecs consécutifs, le point de terminaison est désactivé et apparaît comme désactivé automatiquement. Les nouvelles tentatives en file d’attente s’arrêtent. Réactivez-le pour réinitialiser le compteur, puis relancez ou rejouez ce qui a échoué.
- Les livraisons sont au moins une fois. Dédoublonnez sur
X-Webhook-Delivery(ou sur l’idCloudEvents). - L’ordre d’arrivée des événements n’est pas garanti. Utilisez les heures contenues dans le payload.
Les événements de connexion et de modification des appareils disposent de leur propre garde-fou de volume : au plus 600 de chaque type par organisation et par minute par défaut. Voir la Webhook event reference.
Renouveler le secret
Sélectionnez Rotate signing secret (rôle Owner). Le nouveau secret n’est affiché qu’une fois. L’ancien secret reste valide 48 heures, et pendant ce temps chaque appel porte les deux signatures. Renouveler à nouveau pendant que cette fenêtre est ouverte ne remplace que le secret actuel ; le précédent conserve sa date de fin.
Test
Depuis les actions de ligne d’un webhook ou sur sa page :
- Send test event envoie immédiatement un événement nommé
testet affiche le résultat (« Test delivered — HTTP 200 in 85 ms »). Un test ne compte jamais dans le disjoncteur. - Trigger event… envoie un événement réel et signé de votre choix avec un exemple de payload (objet JSON, 64 Ko au maximum), marqué
manual: trueetX-Webhook-Manual: true. Il respecte le filtre d’événements du point de terminaison : un exemple que le filtre écarterait est refusé. - View deliveries liste les livraisons ; une livraison échouée ou en lettre morte peut être renvoyée, et toutes les lettres mortes peuvent être rejouées en une fois.
Règles réseau
Les adresses des webhooks doivent être des adresses HTTPS publiques. Les adresses privées, de boucle locale, à liaison locale et de métadonnées cloud sont refusées à l’enregistrement et de nouveau à chaque appel, et les redirections ne sont pas suivies. Pour une cible interne voulue, un administrateur peut définir un ipAllowlist explicite (jusqu’à 50 adresses ou plages CIDR) sur le webhook via l’API.
L’API des webhooks
Les chemins commencent par /tenant/<tenantId>. Tous nécessitent les droits d’un administrateur.
| Méthode | Chemin | Action |
|---|---|---|
| GET | /api/webhooks/events | Les événements auxquels vous pouvez vous abonner |
| GET, POST | /api/webhooks | Liste (page à partir de 1, pageSize, search, sort, isActive, autoDisabled) ; création : 201 { webhook, secret } |
| GET, PATCH, DELETE | /api/webhooks/{webhookId} | Lire, modifier, supprimer |
| POST | /api/webhooks/{webhookId}/rotate-secret | { webhook, newSecret, overlapMs } (Owner) |
| POST | /api/webhooks/{webhookId}/test | { delivery } |
| POST | /api/webhooks/{webhookId}/trigger | { event, payload } → { message, deliveryId } |
| GET | /api/webhooks/{webhookId}/deliveries | { data, total } ; filtres status, event, createdFrom, createdTo |
| POST | /api/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver | Remettre en file une livraison FAILED ou DEAD_LETTERED |
| GET, POST | /api/webhooks/{webhookId}/deliveries/replay-dead-letter | Compter, ou remettre en file, chaque dead letter |
| GET | /api/webhooks/metrics, /api/webhooks/report | Statistiques de livraison (?webhookId=&days=) |
Corps de création : name, description, url, events, headers, eventFilters, tags, rateLimitPerMinute (1 à 100,000), ipAllowlist, format (json ou cloudevents).
| Refus | Signification |
|---|---|
403 WEBHOOKS_NOT_IN_PLAN | Votre offre ne comprend pas les webhooks |
409 ENDPOINT_DISABLED | L’endpoint est désactivé ; activez-le d’abord |
409 DELIVERY_NOT_REDELIVERABLE | La livraison est en attente ou a déjà réussi |
422 EVENT_FILTERED_OUT | Le filtre de l’endpoint éliminerait l’exemple que vous avez déclenché |
422 | Un événement qu’aucun module de votre organisation ne propose |

