Webhooks
Recevez des notifications d'événements signées lorsque vos documents sont indexés, que vos livraisons échouent ou que vos points de terminaison changent d'état. Vérification, nouvelles tentatives et tests.
Les webhooks envoient des notifications d'événements à vos systèmes sous forme de requêtes HTTPS POST, ce qui vous permet de réagir aux changements sans avoir à interroger (polling) le système. Enregistrez un point de terminaison depuis les paramètres de votre espace de travail : l'URL doit utiliser HTTPS, et chaque espace de travail peut enregistrer jusqu'à 10 points de terminaison. Lors de la création, vous recevez une clé de signature (préfixée whsec_, au format Standard Webhooks) une seule fois ; elle n'est jamais récupérable par la suite, alors copiez-la immédiatement dans votre magasin de secrets.
Chaque envoi est un corps JSON avec la même enveloppe :
{
"event": "documents.indexed",
"timestamp": "2026-07-23T09:14:07.000Z",
"tenantId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"sequence": 42,
"data": { "count": 12 }
}Les charges utiles sont délibérément légères : uniquement des identifiants, des comptes et des horodatages. Considérez un webhook comme un signal indiquant qu'un changement s'est produit, puis récupérez l'état actuel via l'API authentifiée. Les noms et le contenu des documents n'apparaissent jamais dans la charge utile d'un webhook.
sequence est un compteur par point de terminaison, dense pour le flux de ce point de terminaison : les événements consécutifs délivrés à ce point de terminaison portent des numéros consécutifs, donc un numéro manquant signifie qu'une délivrance n'a jamais été reçue par votre service. L'ORDRE de délivrance n'est pas garanti, utilisez donc la séquence (et non l'ordre d'arrivée) pour ordonner les événements. Deux mises en garde : les rejouages arrivent délibérément hors ordre de séquence (un événement rattrapé conserve sa séquence originale, plus ancienne), alors dédupliquez sur webhook-id et ne rejetez jamais une délivrance simplement parce que sa séquence est inférieure à votre marque haute ; et un écart peut aussi signifier que l'événement manquant est encore en cours de nouvelle tentative ou en attente dans un état rejouable, alors traitez les écarts comme "vérifiez le journal de délivrance", et non comme une preuve de perte.
Catalogue des événements
Abonnez un point de terminaison à toute combinaison des types d'événements ci-dessous. Les types marqués prévu peuvent déjà être sélectionnés, mais rien ne se déclenche pour eux pour l'instant et la forme de leur charge utile n'est pas finale ; elle est publiée ici lorsque l'événement devient actif.
| Événement | Statut | Charge utile (data) |
|---|---|---|
documents.indexed | Actif | Des documents sont devenus consultables. Agrégé par espace de travail : au plus un événement par fenêtre d'agrégation, portant un compte. |
documents.failed | Actif | Des documents ont échoué définitivement lors du traitement. Agrégé par espace de travail : au plus un événement par fenêtre d'agrégation, portant un compte. |
sync.completed | Prévu | Une synchronisation de connecteur s'est terminée. La forme de la charge utile n'est pas finale. |
sync.failed | Prévu | Une synchronisation de connecteur a échoué. La forme de la charge utile n'est pas finale. |
ingestion.completed | Prévu | Une ingestion s'est terminée. La forme de la charge utile n'est pas finale. |
ingestion.failed | Prévu | Une ingestion a échoué. La forme de la charge utile n'est pas finale. |
audit.recorded | Actif | Le journal d'audit de l'espace de travail a reçu de nouvelles entrées. Agrégé par espace de travail sur une fenêtre : un compte et la fenêtre à extraire, jamais les entrées elles-mêmes. |
endpoint.disabled | Actif | Un point de terminaison de webhook a été désactivé : automatiquement après des échecs de délivrance soutenus ou une réponse 410 Gone, ou par un administrateur. Envoyé aux autres points de terminaison de l'espace de travail. |
delivery.failing | Actif | Les délivrances vers un point de terminaison de webhook échouent : déclenché une fois par fenêtre d'échec, 30 minutes après la première tentative échouée, et envoyé aux autres points de terminaison de l'espace de travail. |
endpoint.enabled | Actif | Un point de terminaison de webhook désactivé a été réactivé. Ferme l'incident ouvert par endpoint.disabled ; envoyé aux autres points de terminaison de l'espace de travail. |
delivery.recovered | Actif | Une délivrance a réussi sur un point de terminaison pour lequel delivery.failing avait été déclenché. Ferme cet incident ; envoyé aux autres points de terminaison de l'espace de travail. |
webhook.ping | Actif | Événement de test dirigé envoyé par l'action d'envoi de test du tableau de bord vers exactement un point de terminaison. Jamais souscriptible ; data est toujours vide. |
Exemples d'objets data pour les événements actifs :
// documents.indexed
{ "count": 12 }
// documents.failed
{ "count": 2 }
// audit.recorded
{
"count": 34,
"windowStart": "2026-07-23T09:05:00.000Z",
"windowEnd": "2026-07-23T09:10:00.000Z"
}
// endpoint.disabled
{
"endpointId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"reason": "sustained_failure",
"disabledAt": "2026-07-23T09:14:07.000Z"
}
// delivery.failing
{
"endpointId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"failingSince": "2026-07-23T09:14:07.000Z"
}
// endpoint.enabled
{
"endpointId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"enabledAt": "2026-07-24T08:02:51.000Z"
}
// delivery.recovered
{
"endpointId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"failingSince": "2026-07-23T09:14:07.000Z",
"recoveredAt": "2026-07-23T11:40:12.000Z"
}
// webhook.ping
{}Les événements opérationnels (delivery.failing, delivery.recovered, endpoint.disabled, endpoint.enabled) rapportent l'état de santé des autres points de terminaison : le point de terminaison concerné par un événement est toujours exclu de la délivrance de cet événement, car y envoyer l'événement serait du bruit garanti.
Chaque événement actif est également décrit dans le document OpenAPI à /api/openapi.json, sous webhooks : le schéma JSON complet du corps et des trois en-têtes de signature, ce qui vous permet de générer des types de récepteur au lieu de copier les exemples ci-dessus. Le schéma est également appliqué de notre côté : une charge utile qui ne le respecte pas n'est jamais envoyée.
Alimentation d'un journal d'audit vers un SIEM
audit.recorded est la moitié push d'une alimentation SIEM, et il ne s'agit délibérément pas des entrées : une entrée d'audit nomme une personne, son adresse et ce qu'elle a modifié, et une charge utile de webhook est stockée dans le journal de délivrance et envoyée à une URL que vous contrôlez, qui peut se résoudre n'importe où. L'événement vous indique qu'une fenêtre s'est fermée avec des entrées ; vous récupérez les entrées via l'API authentifiée avec une clé portant la portée audit:read.
À chaque événement, recherchez le journal avec windowStart comme startDate et windowEnd comme endDate, en paginant sur le curseur jusqu'à ce qu'il revienne nul. La borne de début de la recherche est inclusive et celle de la fenêtre ne l'est pas, donc une page peut répéter l'entrée à cet instant précis ; dédupliquez sur l'identifiant de l'entrée, dont vous avez besoin dans tous les cas car la délivrance est au-moins-une-fois.
Le point de terminaison du catalogue liste chaque action que le journal peut enregistrer, avec la catégorie à laquelle chacune appartient et une version qui change lorsque le vocabulaire évolue. Lisez-le une fois, comparez la version lors des lectures ultérieures, et traitez une action absente du catalogue comme inconnue plutôt qu'invalide : le journal est en appendice seulement et conserve l'orthographe avec laquelle chaque entrée a été écrite.
Les fenêtres sont contiguës tant que vous restez abonné : chacune commence là où la dernière événement que vous avez reçu s'est terminée, donc un passage retardé élargit la fenêtre suivante au lieu de perdre ce qui s'est passé entre-temps. Une fenêtre sans entrées n'envoie rien.
Vérification des délivrances
Chaque délivrance est signée selon la spécification Standard Webhooks. Vérifiez toujours la signature avant de faire confiance à une requête : l'URL de votre point de terminaison est accessible par quiconque sur Internet.
Chaque requête contient trois en-têtes :
| En-tête | Valeur |
|---|---|
webhook-id | Identifiant du message. Identique sur chaque nouvelle tentative, renvoi et rejouage du même événement ; dédupliquez sur celui-ci. |
webhook-timestamp | Horodatage Unix en secondes au moment de l'envoi. |
webhook-signature | Une ou plusieurs signatures, séparées par des espaces, chacune sous la forme v1,{base64}. |
Pour vérifier :
- Reconstituez le contenu signé comme
{webhook-id}.{webhook-timestamp}.{raw body}. Utilisez les octets bruts du corps de la requête exactement tels que reçus, avant tout parsing JSON. - Calculez HMAC-SHA256 sur cette chaîne. La clé est la partie décodée en base64 du secret après le préfixe
whsec_(la dérivation de clé Standard Webhooks, qui est ce que les bibliothèques de référence implémentent). - Encodez le HMAC en base64 et comparez-le à chaque signature
v1,dans l'en-tête en utilisant une comparaison à temps constant. La délivrance est authentique si l'une d'elles correspond. - Rejetez les délivrances dont le
webhook-timestampest à plus de 5 minutes de votre heure actuelle, dans un sens ou dans l'autre. Cela limite le rejouage des requêtes capturées.
L'en-tête peut contenir plus d'une signature : pendant qu'une fenêtre de grâce de rotation de secret est ouverte, les délivrances sont signées avec à la fois le nouveau et l'ancien secret (v1,NEW_SIG v1,OLD_SIG), donc la vérification continue de réussir quel que soit le secret vers lequel votre service a basculé.
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 5 * 60;
/**
* @param {string} secret - The whsec_ signing secret from endpoint creation.
* @param {Record<string, string>} headers - Lowercased request headers.
* @param {string} rawBody - The raw request body, unparsed.
*/
export function verifyWebhook(secret, headers, rawBody) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const signatureHeader = headers["webhook-signature"];
if (!id || !timestamp || !signatureHeader) return false;
const skew = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!Number.isFinite(skew) || skew > TOLERANCE_SECONDS) return false;
const key = Buffer.from(secret.slice("whsec_".length), "base64");
const expected = createHmac("sha256", key)
.update(`${id}.${timestamp}.${rawBody}`)
.digest();
return signatureHeader.split(" ").some((entry) => {
const comma = entry.indexOf(",");
if (comma === -1 || entry.slice(0, comma) !== "v1") return false;
const received = Buffer.from(entry.slice(comma + 1), "base64");
return received.length === expected.length && timingSafeEqual(received, expected);
});
}Comme le format suit la spécification Standard Webhooks, les bibliothèques de vérification open source pour d'autres langages fonctionnent telles quelles ; passez-leur le secret et les trois en-têtes.
Nouvelles tentatives et échec
Votre point de terminaison dispose de 10 secondes pour répondre, y compris l'établissement de la connexion. Une réponse avec un statut 2xx compte comme une délivrance réussie ; tout autre statut, y compris un délai d'attente ou une redirection (les délivrances ne suivent jamais les redirections), compte comme un échec et est retenté. La seule exception est 410 Gone : il nous indique que la route a été supprimée volontairement, donc cette délivrance n'est pas retentée et le point de terminaison est désactivé immédiatement, avec un événement endpoint.disabled (raison gone) envoyé aux autres points de terminaison de l'espace de travail et un email à chaque propriétaire et administrateur. Un 410 en réponse à un envoi de test ou à un renvoi manuel échoue uniquement cette délivrance.
Répondez 2xx dès que vous avez accepté l'événement de manière durable, et effectuez le traitement lourd de manière asynchrone.
Une délivrance échouée est retentée selon ce calendrier :
| Tentative | Délai après l'échec précédent |
|---|---|
| 1 | immédiat |
| 2 | 5 secondes |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 heures |
| 6 | 5 heures |
| 7 | 10 heures |
| 8 | 10 heures |
Cela représente 8 tentatives sur environ 27,5 heures, donc une panne d'une journée complète de votre côté ne fait pas perdre d'événements. Chaque délai comporte jusqu'à 20 % de gigue aléatoire dans un sens ou dans l'autre, ce qui empêche des rafales de nouvelles tentatives synchronisées contre un point de terminaison en cours de récupération. Si votre point de terminaison retourne un en-tête Retry-After, le délai demandé est respecté dans la limite des bornes : il est compris entre le délai prévu et le double du délai prévu. Les réponses avec le statut 429 et les tentatives expirées ne sont pas retentées avant 60 secondes, quel que soit le moment où le créneau prévu tombe.
Désactivation automatique
Un point de terminaison qui continue d'échouer est finalement désactivé plutôt que d'être sollicité indéfiniment :
- Lorsque les délivrances échouent depuis 30 minutes, un événement
delivery.failingse déclenche une fois (envoyé aux autres points de terminaison de l'espace de travail) et chaque propriétaire et administrateur de l'espace de travail reçoit une notification dans l'application. Il s'agit de l'avertissement précoce ; un incident de courte durée qu'une réussite ferme dans ces 30 minutes ne déclenche rien. - À mesure que la fenêtre d'échec se poursuit, l'avertissement s'intensifie : à 3 jours et à nouveau à 4,5 jours, chaque propriétaire et administrateur reçoit une notification dans l'application et un email indiquant l'heure après laquelle le point de terminaison sera désactivé si les délivrances continuent d'échouer. Chaque avertissement atteint chaque personne une fois par fenêtre d'échec.
- Un événement délivré avec succès réinitialise la fenêtre d'échec. Un événement de test ne le fait pas : il prouve que le point de terminaison répond, pas qu'il traite les événements. Si un avertissement avait déjà été envoyé, un événement
delivery.recoveredse déclenche et les propriétaires et administrateurs reçoivent une notification dans l'application indiquant que le point de terminaison s'est rétabli. - Après 5 jours d'échecs consécutifs, le point de terminaison est désactivé : un événement
endpoint.disabledse déclenche (à nouveau, vers les autres points de terminaison), et chaque propriétaire et administrateur de l'espace de travail est averti par email.
La désactivation nécessite une délivrance échouée après le dernier avertissement. Si rien n'a été envoyé au point de terminaison au moment où le dernier avertissement a été nommé, le prochain échec déclenche un nouvel avertissement final, et le point de terminaison n'est désactivé que si les délivrances continuent d'échouer 12 heures plus tard.
Désactiver vous-même un point de terminaison déclenche endpoint.disabled avec la raison manual vers les autres points de terminaison de l'espace de travail. Un point de terminaison désactivé ne reçoit plus de délivrances, et ses actions d'envoi de test et de renvoi sont bloquées. Une fois que votre récepteur est de nouveau opérationnel, réactivez le point de terminaison depuis les paramètres de l'espace de travail (cliquez sur son badge de statut) : la réactivation efface l'historique des échecs, donc le point de terminaison commence une nouvelle fenêtre de 5 jours, et un événement endpoint.enabled est envoyé aux autres points de terminaison de l'espace de travail. Les événements qui se produisent alors qu'un point de terminaison est désactivé sont enregistrés dans son journal de délivrance avec le statut abandonné au lieu d'être envoyés. Après la réactivation, utilisez Envoyer un test pour confirmer que le point de terminaison est accessible, puis Rejouer les échecs + abandonnés pour rattraper tout ce que la panne a coûté en une seule action.
Renvoi et test manuels
Le journal de délivrance dans les paramètres de l'espace de travail affiche chaque tentative de délivrance avec son code de statut et sa réponse. À partir de là :
- Renvoyer renvoie l'événement original d'une délivrance passée (même
webhook-id, même contenu JSON) en une seule tentative. Il n'entre pas dans le calendrier de nouvelles tentatives et ne compte jamais pour la désactivation automatique, il est donc sûr à utiliser lors du débogage d'un récepteur instable. Le renvoi porte lewebhook-idoriginal, donc un récepteur qui a déjà traité l'événement avant de répondre avec une erreur le traite comme le même message. - Rejouer les échecs + abandonnés rejoue en masse chaque délivrance définitivement échouée et abandonnée du point de terminaison, de la plus ancienne à la plus récente (les délivrances encore dans leur calendrier de nouvelles tentatives automatiques sont exclues, donc un rejouage ne peut jamais dupliquer une nouvelle tentative qui allait de toute façon réussir). Les délivrances rejouées utilisent le calendrier de nouvelles tentatives complet et portent le
webhook-idoriginal : pour votre service, un rejouage est le même message à nouveau, donc la gestion de l'idempotence surwebhook-idrend le rattrapage sûr, que l'original soit jamais arrivé ou non. Un événement qu'un renvoi ou un rejouage précédent a déjà délivré est ignoré, tout comme un événement encore en cours de délivrance. Choisissez jusqu'où remonter (les dernières 24 heures, les 7 derniers jours ou tout ce qui est conservé), et la boîte de dialogue prévisualise combien d'événements cette plage enverra avant que vous ne commenciez ; les grandes plages sont paginées automatiquement, avec une indication de la progression au fur et à mesure. Les rejouages arrivent hors ordre de séquence ; ordonnez les événements rattrapés par lesequencede la charge utile. - Envoyer un test envoie un événement
webhook.pingsigné avec un objetdatavide à exactement ce point de terminaison, indépendamment de ses abonnements aux événements, et affiche le résultat (le code de statut, ou la raison de l'échec) dès que la tentative est terminée. Utilisez-le pour confirmer l'accessibilité et pour tester votre vérification de signature de bout en bout. Un événement de test n'ouvre, ne fait pas avancer ou ne réinitialise jamais la fenêtre d'échec.
Bonnes pratiques
- Dédoublonnez sur
webhook-id. Les nouvelles tentatives automatiques, les renvois et les rejouages réutilisent le même identifiant, donc le stockage des identifiants traités vous donne un traitement exactement-une-fois par-dessus une délivrance au-moins-une-fois. - Ne traitez jamais une charge utile comme un état actuel. Les charges utiles sont légères et peuvent arriver en retard ; utilisez l'événement comme déclencheur et lisez l'état actuel depuis l'API.
- Ne vous fiez pas à l'ordre. Les nouvelles tentatives et la délivrance parallèle signifient que les événements peuvent arriver dans le désordre.
- Répondez 2xx avant le travail lourd. Mettez l'événement en file d'attente et traitez-le de manière asynchrone ; un gestionnaire qui effectue son travail en ligne risque de dépasser le délai de 10 secondes et d'être retenté, ce qui transforme un événement en plusieurs tentatives en double.
- Faites tourner les secrets avec une fenêtre de grâce. La rotation depuis les paramètres de l'espace de travail maintient le secret précédent valide pendant une fenêtre que vous choisissez, et les délivrances sont signées avec les deux secrets pendant celle-ci, donc un déploiement progressif de vos services ne fait jamais perdre de délivrance. Un point de terminaison conserve un secret précédent, donc une deuxième rotation avec une fenêtre de grâce est refusée jusqu'à ce que la première fenêtre soit terminée ; une rotation sans fenêtre de grâce est toujours acceptée et révoque tous les anciens secrets immédiatement, ce qui est la solution en cas de fuite de secret.
Cette page vous a‑t‑elle été utile ?
MCP
Connectez un agent IA à votre base de connaissances de l'espace de travail via le Model Context Protocol avec une clé API.
Documents et recherche
Les documents constituent l'unité interrogeable dans Nordvec. Ingestez-les, puis effectuez des requêtes avec une recherche hybride en texte intégral et sémantique.