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.
Prérequis
Section intitulée « Prérequis »- Un compte DIMARC actif.
- Être administrateur de votre organisation.
- Votre token d’authentification
x-api-key(voir Récupération de votre Token d’Authentification)
Format de réponse
Section intitulée « Format de réponse »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).
Événements
Section intitulée « Événements »document.ingestion_completed
Section intitulée « document.ingestion_completed »É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" }}extraction.completed
Section intitulée « extraction.completed »É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 } }}classification.completed
Section intitulée « classification.completed »É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 } }}Enveloppe des événements
Section intitulée « Enveloppe des événements »Chaque événement livré à votre URL a la structure suivante :
| Champ | Type | Description |
|---|---|---|
id | string | Identifiant unique de la livraison (ULID) — utilisez-le pour dédupliquer |
event | string | document.ingestion_completed, extraction.completed ou classification.completed |
timestamp | string | Date d’émission, ISO 8601 UTC |
data | object | Contenu spécifique à l’événement, décrit ci-dessus |
Configurer un webhook
Section intitulée « Configurer un webhook »Créer ou mettre à jour
Section intitulée « Créer ou mettre à jour »PUT /v2/organization/webhooks/{event}
{event} : document.ingestion_completed, extraction.completed ou classification.completed.
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 URLhttps://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).
Lister les webhooks configurés
Section intitulée « Lister les webhooks configurés »GET /v2/organization/webhooks
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).
Supprimer un webhook
Section intitulée « Supprimer un webhook »DELETE /v2/organization/webhooks/{event}
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).
Rotation du secret
Section intitulée « Rotation du secret »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 :
DELETE /v2/organization/webhooks/{event}PUT /v2/organization/webhooks/{event}avec la même URL (ou une nouvelle) — un nouveau secret est généré et renvoyé une fois.
Vérification de la signature
Section intitulée « Vérification de la signature »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.
Politique de retries
Section intitulée « Politique de retries »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 :
| Tentative | Délai avant l’essai |
|---|---|
| 1 (initiale) | — |
| 2 | 1 seconde |
| 3 | 10 secondes |
| 4 | 60 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).
Support et assistance
Section intitulée « Support et assistance »Pour toute question concernant les webhooks, contactez notre équipe de support à l’adresse contact@dimarc.fr