Aller au contenu

Webhooks

Ce document décrit la configuration des webhooks d’organisation : événements disponibles, gestion (création, consultation, suppression) et vérification des livraisons. Ces endpoints sont scopés à votre organisation via votre token x-api-key (voir Récupération de votre Token d’Authentification).

Un webhook est configuré par événement : une organisation peut avoir au maximum une URL configurée pour document.ingestion_completed, une pour extraction.completed et une pour classification.completed.

Les endpoints de configuration utilisent l’enveloppe commune de l’API :

{ "status": "success", "data": { } }
{ "status": "error", "code": "webhook_not_found", "error": "Webhook not found" }

Codes d’erreur : agent_not_found (organisation introuvable), webhook_not_found, invalid_request (URL invalide ou non autorisée).

Émis à l’issue du traitement d’un document ingéré via l’API (POST /v2/agents/{reference}/documents). Les documents uploadés depuis l’application web ne déclenchent pas cet événement (ils sont suivis via l’interface).

{
"id": "01J...",
"event": "document.ingestion_completed",
"timestamp": "2026-07-22T10:15:30.000Z",
"data": {
"agent_reference": "01ABC...",
"document_reference": "01XYZ...",
"status": "ready",
"name": "rapport-annuel.pdf",
"path": "finance/2026"
}
}
{
"id": "01J...",
"event": "document.ingestion_completed",
"timestamp": "2026-07-22T10:16:05.000Z",
"data": {
"agent_reference": "01ABC...",
"document_reference": "01XYZ...",
"status": "error",
"name": "rapport-annuel.pdf",
"path": "finance/2026"
}
}

Émis à l’issue d’une extraction asynchrone (POST /v2/extractor/async ou POST /v2/extractor/async/{agentReference}), en complément du callback par requête décrit dans la documentation de l’Extracteur.

{
"id": "01J...",
"event": "extraction.completed",
"timestamp": "2026-07-22T10:20:00.000Z",
"data": {
"status": "success",
"extract_id": "550e8400-e29b-41d4-a716-446655440000",
"data": [
{
"invoice_number": "INV-2024-001",
"total_amount": 1250.5
}
],
"metadata": {
"filename": "facture.pdf",
"duration": 3.45,
"tokens_consumed": 1,
"thread_id": "01J..."
}
}
}
{
"id": "01J...",
"event": "extraction.completed",
"timestamp": "2026-07-22T10:20:00.000Z",
"data": {
"status": "error",
"extract_id": "550e8400-e29b-41d4-a716-446655440000",
"code": "agent_timeout",
"error": "Agent response timed out",
"detail": { "http_status": 504 }
}
}

Émis à l’issue d’une classification asynchrone (POST /v2/classification/async), en complément du callback par requête décrit dans la documentation de la Classification.

{
"id": "01J...",
"event": "classification.completed",
"timestamp": "2026-07-22T10:20:00.000Z",
"data": {
"status": "success",
"classification_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"source_name": "lot-documents.pdf",
"mode": "document",
"segments": [
{
"start": 1,
"end": 3,
"label": "facture",
"title": "Facture n°2024-001",
"confidence": 0.92
}
],
"discovered_classes": [],
"pages_count": 3
},
"metadata": {
"filename": "lot-documents.pdf",
"duration": 4.12,
"tokens_consumed": 1,
"thread_id": "01J..."
}
}
}
{
"id": "01J...",
"event": "classification.completed",
"timestamp": "2026-07-22T10:20:00.000Z",
"data": {
"status": "error",
"classification_id": "550e8400-e29b-41d4-a716-446655440000",
"code": "agent_timeout",
"error": "Agent response timed out",
"detail": { "http_status": 504 }
}
}

Chaque événement livré à votre URL a la structure suivante :

ChampTypeDescription
idstringIdentifiant unique de la livraison (ULID) — utilisez-le pour dédupliquer
eventstringdocument.ingestion_completed, extraction.completed ou classification.completed
timestampstringDate d’émission, ISO 8601 UTC
dataobjectContenu spécifique à l’événement, décrit ci-dessus

PUT /v2/organization/webhooks/{event}

{event} : document.ingestion_completed, extraction.completed ou classification.completed.

Fenêtre de terminal
curl --request PUT 'https://api.dimarc.ai/v2/organization/webhooks/document.ingestion_completed' \
--header 'x-api-key: <your_api_key>' \
--header 'Content-Type: application/json' \
--data '{ "url": "https://votre-domaine.com/webhooks/dimarc" }'
  • url (obligatoire) : doit être une URL https:// publique. Les URL pointant vers une IP privée, une boucle locale ou un hostname interne sont rejetées (422 invalid_request).

Première configuration — réponse 201, le secret n’est renvoyé qu’une seule fois :

{
"status": "success",
"data": {
"event": "document.ingestion_completed",
"url": "https://votre-domaine.com/webhooks/dimarc",
"secret": "whsec_dimarc_..."
}
}

Mise à jour de l’URL (webhook déjà configuré pour cet événement) — réponse 200, sans secret :

{
"status": "success",
"data": {
"event": "document.ingestion_completed",
"url": "https://votre-domaine.com/webhooks/dimarc-v2"
}
}

Erreurs possibles : 404 agent_not_found (organisation introuvable), 422 invalid_request (URL invalide ou non autorisée).

GET /v2/organization/webhooks

Fenêtre de terminal
curl 'https://api.dimarc.ai/v2/organization/webhooks' \
--header 'x-api-key: <your_api_key>'
{
"status": "success",
"data": [
{
"event": "document.ingestion_completed",
"url": "https://votre-domaine.com/webhooks/dimarc",
"created_at": "2026-07-01T09:00:00.000Z",
"updated_at": "2026-07-01T09:00:00.000Z"
}
]
}

Le secret n’est jamais renvoyé par cet endpoint.

Erreurs possibles : 404 agent_not_found (organisation introuvable).

DELETE /v2/organization/webhooks/{event}

Fenêtre de terminal
curl --request DELETE 'https://api.dimarc.ai/v2/organization/webhooks/document.ingestion_completed' \
--header 'x-api-key: <your_api_key>'
{ "status": "success", "data": { "event": "document.ingestion_completed" } }

Erreurs possibles : 404 agent_not_found (organisation introuvable), 404 webhook_not_found (aucun webhook configuré pour cet événement).

Il n’existe pas d’endpoint dédié à la rotation : un PUT sur un webhook déjà configuré ne fait que changer l’URL, il ne régénère jamais le secret. Pour obtenir un nouveau secret :

  1. DELETE /v2/organization/webhooks/{event}
  2. PUT /v2/organization/webhooks/{event} avec la même URL (ou une nouvelle) — un nouveau secret est généré et renvoyé une fois.

Chaque livraison porte un en-tête x-dimarc-signature :

x-dimarc-signature: sha256=<hex HMAC-SHA256(secret, corps_brut)>

La signature est calculée sur le corps brut de la requête (les octets exacts envoyés, avant tout parsing JSON). Vérifiez-la avant de désérialiser :

import { createHmac, timingSafeEqual } from "node:crypto";
const seenEventIds = new Set(); // remplacez par un store persistant (Redis, DB...)
function verifyDimarcWebhook(rawBody, signatureHeader, secret) {
const expected =
"sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const received = Buffer.from(signatureHeader ?? "", "utf8");
const expectedBuf = Buffer.from(expected, "utf8");
if (
received.length !== expectedBuf.length ||
!timingSafeEqual(received, expectedBuf)
) {
throw new Error("Invalid webhook signature");
}
const event = JSON.parse(rawBody);
// Tolérance recommandée : 5 minutes, pour se prémunir du rejeu d'une requête interceptée
const ageMs = Date.now() - new Date(event.timestamp).getTime();
if (Math.abs(ageMs) > 5 * 60 * 1000) {
throw new Error("Webhook timestamp outside tolerance");
}
// Déduplication par id — un même événement peut être livré plusieurs fois (retries)
if (seenEventIds.has(event.id)) {
return null; // déjà traité, ignorer silencieusement
}
seenEventIds.add(event.id);
return event;
}

Points clés :

  • Comparaison en temps constant (timingSafeEqual) — une comparaison directe de chaînes de caractères expose à une attaque par mesure de temps.
  • Corps brut — si votre framework parse le JSON avant que vous n’ayez accès au corps brut (ex. middleware par défaut d’Express), configurez-le pour exposer le buffer non parsé sur cette route.
  • Déduplication par id — les retries peuvent livrer deux fois le même événement ; votre traitement doit être idempotent.
  • Tolérance de timestamp — 5 minutes est recommandé, pour rejeter une requête rejouée bien après coup sans être trop strict sur la latence réseau normale.

En cas d’échec de livraison (URL injoignable, timeout, ou réponse 429/5xx), la livraison est retentée jusqu’à 3 fois après l’essai initial, avec un backoff croissant :

TentativeDélai avant l’essai
1 (initiale)
21 seconde
310 secondes
460 secondes

Une réponse 4xx autre que 429 est considérée comme un échec permanent — la livraison n’est pas retentée. Assurez-vous que votre endpoint répond 2xx dès que la signature est valide et l’événement accepté, y compris si son traitement métier est différé.

Passé la dernière tentative, la livraison est abandonnée : il n’y a pas de file de rejeu ni de re-livraison manuelle. Le webhook n’est qu’un canal best-effort — pour un statut garanti, combinez-le avec le polling (GET /v2/agents/{reference}/documents).

Pour toute question concernant les webhooks, contactez notre équipe de support à l’adresse contact@dimarc.fr