Aller au contenu

Documentation de l’API Fileverity

Webhooks

Recevez les événements analysis.completed, analysis.failed et balance.low. Vérification des signatures Standard Webhooks en JavaScript et en Python, nouvelles tentatives, désactivation automatique et événement de test.

Voir en MarkdownMis à jour le 2026-11-04

Les webhooks envoient une requête HTTPS à votre serveur lorsqu’un événement se produit, afin que vous n’ayez pas à interroger l’API. Ils suivent la spécification Standard Webhooks.

Ajoutez des points de terminaison dans la Console, sous Webhooks. Choisissez l’URL et les événements qu’elle reçoit. Le secret de signature, qui commence par whsec_, n’est affiché qu’une seule fois, à la création du point de terminaison. Vous pouvez modifier à tout moment les événements d’un point de terminaison sans changer son secret.

Pour remplacer un secret perdu ou exposé, choisissez Renouveler le secret sur le point de terminaison. Le nouveau secret n’est affiché qu’une seule fois et l’ancien cesse de fonctionner immédiatement : mettez donc à jour votre code de vérification sans attendre.

  • L’URL doit utiliser HTTPS sur le port par défaut 443 et pointer vers une adresse publique. Les URL contenant des identifiants ou un autre port sont rejetées.
  • Un espace de travail peut compter jusqu’à 10 points de terminaison.
  • Les clés de test n’envoient jamais d’événements.

Événements

ÉvénementEnvoyé lorsque
analysis.completedL’analyse d’une image ou d’un document a réussi.
analysis.failedUne analyse a échoué ou a été annulée. Elle n’a pas été facturée. Une analyse annulée porte status: canceled et error: canceled.
balance.lowLe solde est passé sous le seuil d’alerte que vous avez défini dans la Console.

Les types d’événements pris en charge sont analysis.completed, analysis.failed, balance.low.

Charges utiles

Chaque événement a la même enveloppe : id, object (toujours event), type, created (horodatage Unix) et data. L’id est aussi envoyé dans l’en-tête webhook-id.

analysis.completed et analysis.failed

data est le même objet d’analyse que celui renvoyé par GET /analyses/{id}, avec les mêmes noms et types de champs, sauf que result vaut toujours null : récupérez le résultat avec ce point d’accès. status vaut succeeded, failed ou canceled, et error est un objet contenant code et message, ou null. Les horodatages sont exprimés en secondes Unix.

{  "id": "evt_Lm8sK3tR6yZ2cXaQ9vB4nP1w",  "object": "event",  "type": "analysis.completed",  "created": 1793700131,  "data": {    "id": "an_8Hk2pQ4vN9sL1xR7cW3mY6zB",    "object": "analysis",    "created": 1793700120,    "model": "fvimg-2-2026-10-30",    "livemode": true,    "status": "succeeded",    "file": {      "name": "claim-photo.jpg",      "bytes": 2481920,      "format": "jpeg"    },    "report_id": "FV-7K2M-9QXD-4T8R",    "report_url": "https://fileverity.com/verify/FV-7K2M-9QXD-4T8R",    "summary": {      "level": "L2",      "assessment": "authentic_untouched",      "action": "accept",      "ai": "very_low",      "edit": "none"    },    "result": null,    "error": null,    "progress": null,    "usage": {      "amount": 199,      "currency": "usd",      "funding": "balance"    },    "metadata": {      "order_id": "A-10492"    },    "completed_at": 1793700131,    "expires_at": 1794304931  }}
{  "id": "evt_Zc4vB7nP1sK8tL3wR6yQm2Xa",  "object": "event",  "type": "analysis.failed",  "created": 1793700131,  "data": {    "id": "an_8Hk2pQ4vN9sL1xR7cW3mY6zB",    "object": "analysis",    "created": 1793700120,    "model": "fvimg-2-2026-10-30",    "livemode": true,    "status": "failed",    "file": {      "name": "claim-photo.jpg",      "bytes": 2481920,      "format": "jpeg"    },    "report_id": null,    "report_url": null,    "summary": null,    "result": null,    "error": {      "code": "analysis_failed",      "message": "The analysis could not be completed. You were not charged; retry it, and contact support with the request ID if it fails again."    },    "progress": null,    "usage": {      "amount": 0,      "currency": "usd",      "funding": null    },    "metadata": null,    "completed_at": 1793700135,    "expires_at": null  }}

balance.low

balance et threshold sont exprimés en cents US.

{  "id": "evt_Lm8sK3tR6yZ2cXaQ9vB4nP1w",  "object": "event",  "type": "balance.low",  "created": 1793700500,  "data": {    "balance": 640,    "threshold": 1000  }}

Vérifier la signature

Chaque requête contient trois en-têtes, tels que les définit la spécification Standard Webhooks :

En-têteValeur
webhook-idL’ID de l’événement. Il reste identique d’une nouvelle tentative à l’autre ; utilisez-le pour ignorer les doublons.
webhook-timestampHorodatage Unix, en secondes, de cette tentative d’envoi. Il change à chaque nouvelle tentative.
webhook-signaturev1, suivi d’un HMAC-SHA256 encodé en base64. Un en-tête peut contenir plusieurs signatures séparées par des espaces.

Pour vérifier une requête :

  1. Lisez le corps brut de la requête sous forme de texte, avant toute désérialisation du JSON.
  2. Construisez le contenu signé : webhook-id, un point, webhook-timestamp, un point et le corps brut.
  3. Prenez votre secret de signature, retirez le préfixe whsec_ et décodez le reste depuis le base64. C’est la clé HMAC.
  4. Calculez le HMAC-SHA256 du contenu signé et encodez-le en base64.
  5. Comparez-le, en temps constant, à chaque signature v1 de l’en-tête.
  6. Rejetez les horodatages qui s’écartent de plus de cinq minutes de votre horloge.
import { createHmac, timingSafeEqual } from "node:crypto"; export function verify(secret, headers, body) {  const id = headers["webhook-id"];  const timestamp = headers["webhook-timestamp"];  const signatures = headers["webhook-signature"];  if (!id || !timestamp || !signatures) throw new Error("Missing webhook headers");  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) throw new Error("Timestamp outside tolerance");  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");  const expected = createHmac("sha256", key).update(id + "." + timestamp + "." + body).digest();  const valid = signatures.split(" ").some((entry) => {    const [version, value] = entry.split(",");    const given = Buffer.from(value ?? "", "base64");    return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);  });  if (!valid) throw new Error("Invalid signature");  return JSON.parse(body);} app.post("/webhooks/fileverity", express.raw({ type: "application/json" }), (req, res) => {  try {    const event = verify(process.env.FILEVERITY_WEBHOOK_SECRET, req.headers, req.body.toString("utf8"));    handle(event);    res.sendStatus(200);  } catch {    res.sendStatus(400);  }});

Vérifiez la signature sur les octets exacts qui ont été envoyés. Désérialiser puis sérialiser à nouveau le JSON modifie ces octets et invalide la signature.

Envoi et nouvelles tentatives

  • Répondez par n’importe quel statut 2xx en moins de 10 secondes. Les redirections ne sont pas suivies et comptent comme des échecs.
  • Toute autre réponse, y compris un délai d’attente dépassé ou une erreur de connexion, planifie une nouvelle tentative avec des délais croissants : 5, 10, 20, 40, 80, 160, 320 et 640 minutes, puis toutes les 12 heures. Les nouvelles tentatives s’arrêtent 3 jours après l’événement. Elles sont exécutées par une tâche planifiée : une nouvelle tentative peut donc arriver plus tard que son délai, jamais plus tôt.
  • Les événements sont livrés au moins une fois et leur ordre n’est pas garanti. Utilisez webhook-id pour ignorer les doublons et récupérez l’analyse si vous avez besoin de son dernier état.
  • La Console liste les 50 derniers envois avec leur statut et leurs erreurs, les conserve 30 jours et vous permet d’en renvoyer un. Ouvrez un envoi pour voir sa charge utile, son temps de réponse et les 2 premiers Ko de la réponse de votre point de terminaison : ne renvoyez donc ni secrets ni données personnelles dans cette réponse. Un renvoi conserve l’ID de l’événement d’origine.
  • Les requêtes ont pour agent utilisateur Fileverity-Webhooks/1.0.

Désactivation automatique

Un point de terminaison qui échoue en continu pendant 3 jours est désactivé. Il ne reçoit alors plus de nouveaux événements, et ses événements non livrés sont marqués comme échoués. Un envoi réussi avant ce délai efface l’échec. Les propriétaires et les administrateurs de l’espace de travail reçoivent un courriel lorsqu’un point de terminaison est désactivé. Réactivez le point de terminaison dans la Console une fois le problème corrigé.

L’événement de test

Utilisez Envoyer un test sur un point de terminaison dans la Console pour livrer immédiatement un événement d’exemple. Il s’agit d’un événement analysis.completed signé, avec data.test défini sur true et sans rapport, ce qui vous permet de vérifier votre URL et votre code de vérification de signature avant l’arrivée des événements réels.

{  "id": "evt_Qm4vB7nP1sK8tL3wR6yZ2cXa",  "object": "event",  "type": "analysis.completed",  "created": 1793700500,  "data": {    "id": "an_Pw3mY6zB8Hk2pQ4vN9sL1xR7",    "object": "analysis",    "created": 1793700120,    "model": "fvimg-2-2026-10-30",    "livemode": true,    "status": "succeeded",    "file": {      "name": "claim-photo.jpg",      "bytes": 2481920,      "format": "jpeg"    },    "report_id": null,    "report_url": null,    "summary": null,    "result": null,    "error": null,    "progress": null,    "usage": {      "amount": 199,      "currency": "usd",      "funding": "balance"    },    "metadata": {      "order_id": "A-10492"    },    "completed_at": 1793700131,    "expires_at": 1794304931,    "test": true  }}

Voir aussi : Analyses, Idempotence et Erreurs.