Ir al contenido

Documentación de la API de Fileverity

Webhooks

Reciba los eventos analysis.completed, analysis.failed y balance.low. Verificación de firmas de Standard Webhooks en JavaScript y Python, reintentos, desactivación automática y el evento de prueba.

Ver como MarkdownActualizado el 2026-11-04

Los webhooks envían una solicitud HTTPS a su servidor cuando ocurre algo, para que no tenga que consultar la API periódicamente. Siguen la especificación Standard Webhooks.

Añada endpoints en la Consola, en Webhooks. Elija la URL y los eventos que recibe. El secreto de firma, que empieza por whsec_, se muestra una sola vez al crear el endpoint. Puede cambiar los eventos de un endpoint en cualquier momento sin cambiar su secreto.

Para sustituir un secreto perdido o expuesto, elija Renovar secreto en el endpoint. El secreto nuevo se muestra una sola vez y el anterior deja de funcionar de inmediato, así que actualice enseguida el código con el que verifica las firmas.

  • La URL debe usar HTTPS en el puerto predeterminado 443 y apuntar a una dirección pública. Se rechazan las URL con credenciales o con otro puerto.
  • Un espacio de trabajo puede tener hasta 10 endpoints.
  • Las claves de prueba nunca envían eventos.

Eventos

EventoSe envía cuando
analysis.completedUn análisis de imagen o de documento se completó correctamente.
analysis.failedUn análisis falló o se canceló. No se cobró. Un análisis cancelado tiene status: canceled y error: canceled.
balance.lowEl saldo cayó por debajo del umbral de alerta que fijó en la Consola.

Los tipos de evento admitidos son analysis.completed, analysis.failed, balance.low.

Contenido de los eventos

Todos los eventos tienen la misma estructura: id, object (siempre event), type, created (marca de tiempo Unix) y data. El id también se envía en el encabezado webhook-id.

analysis.completed y analysis.failed

data es el mismo objeto de análisis que devuelve GET /analyses/{id}, con los mismos nombres de campo y tipos, salvo que result es siempre null: obtenga el resultado con ese endpoint. status es succeeded, failed o canceled, y error es un objeto con code y message, o null. Las marcas de tiempo se expresan en segundos 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 y threshold están en centavos de dólar estadounidense.

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

Verificar la firma

Cada solicitud incluye tres encabezados, según define la especificación Standard Webhooks:

EncabezadoValor
webhook-idEl ID del evento. Es el mismo en todos los reintentos; úselo para ignorar duplicados.
webhook-timestampHora Unix, en segundos, de este intento de envío. Cambia en cada reintento.
webhook-signaturev1, seguido de un HMAC-SHA256 en base64. Un encabezado puede incluir varias firmas separadas por espacios.

Para verificar una solicitud:

  1. Lea el cuerpo sin procesar de la solicitud como texto, antes de analizar el JSON.
  2. Construya el contenido firmado: webhook-id, un punto, webhook-timestamp, un punto y el cuerpo sin procesar.
  3. Tome su secreto de firma, quite el prefijo whsec_ y decodifique el resto desde base64. Esa es la clave HMAC.
  4. Calcule el HMAC-SHA256 del contenido firmado y codifíquelo en base64.
  5. Compárelo, en tiempo constante, con cada firma v1 del encabezado.
  6. Rechace las marcas de tiempo que difieran en más de cinco minutos de su reloj.
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);  }});

Verifique con los bytes exactos que se enviaron. Analizar el JSON y volver a serializarlo los modifica e invalida la firma.

Envío y reintentos

  • Responda con cualquier estado 2xx en un plazo de 10 segundos. No se siguen las redirecciones y se cuentan como fallos.
  • Cualquier otra respuesta, incluidos un tiempo de espera agotado o un error de conexión, programa un reintento con esperas crecientes: 5, 10, 20, 40, 80, 160, 320 y 640 minutos y, después, cada 12 horas. Los reintentos terminan 3 días después del evento. Los reintentos se ejecutan de forma programada, así que un reintento puede llegar más tarde que su espera, nunca antes.
  • Los eventos se envían al menos una vez y no se garantiza el orden. Use webhook-id para ignorar duplicados y obtenga el análisis si necesita el estado más reciente.
  • La Consola muestra los últimos 50 envíos con su estado y sus errores, los conserva 30 días y permite reenviar cualquiera de ellos. Abra un envío para ver el contenido de la solicitud, el tiempo de respuesta y los primeros 2 KB de la respuesta de su endpoint, así que no devuelva secretos ni datos personales en esa respuesta. Un reenvío conserva el ID del evento original.
  • Las solicitudes llevan el agente de usuario Fileverity-Webhooks/1.0.

Desactivación automática

Un endpoint que falla de forma continua durante 3 días se desactiva. Entonces deja de recibir eventos nuevos y sus eventos no entregados se marcan como fallidos. Un envío correcto antes de ese plazo borra el fallo. Se avisa por correo a los propietarios y administradores del espacio de trabajo cuando se desactiva un endpoint. Vuelva a activar el endpoint en la Consola cuando lo haya corregido.

El evento de prueba

Use Enviar prueba en un endpoint de la Consola para enviar de inmediato un evento de ejemplo. Es un evento analysis.completed firmado, con data.test establecido en true y sin informe, de modo que pueda comprobar su URL y su código de verificación de firmas antes de que lleguen eventos reales.

{  "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  }}

Relacionado: Análisis, Idempotencia y Errores.