Constaia
Guías por caso

Revisión humana

Qué hacer con el veredicto review. Por qué ocurre, cómo recibirlo por webhook, montar una cola de revisión, registrar la decisión y pedir otra foto.

El veredicto Revisar significa que Constaia no puede decidir con seguridad: la foto está borrosa, un dato no se lee o la confianza es baja. No es un rechazo. La mayoría de las veces el documento es correcto y basta con que una persona lo mire o con pedir otra foto.

Esta guía monta ese circuito en tu aplicación: detectar el review, ponerlo en una cola, enseñarlo a un revisor, guardar su decisión y, si hace falta, pedir un documento nuevo.

Cuándo sale review

El veredicto es review cuando hay algún motivo con severity: "warning" y ninguno con error:

code con warningCausa
low_qualityHay avisos de calidad o de autenticidad en warnings (ver tabla de abajo).
low_confidenceLa confianza en el tipo o en los campos clave es baja: "La confianza es baja (0.62); conviene revisarlo a mano.".
type_unknownNo se ha podido identificar el tipo de documento.
not_expired, max_age_days, agePediste el check pero no se encuentra la fecha de caducidad, de emisión o de nacimiento.
holderPediste comparar un dato del titular y no se ha podido leer.

Si la imagen es tan mala que se rechaza antes del OCR, el análisis termina con document: null, review y el motivo low_quality. Esos análisis no se cobran.

Avisos que pueden aparecer en warnings:

CódigoSignificado
low_qualityCalidad baja en general.
blurryImagen desenfocada.
croppedEl documento está recortado.
glareReflejos que tapan datos.
screen_photo_suspectedPosible foto de una pantalla.
photocopy_suspectedPosible fotocopia.
edited_suspectedPosible edición digital.
multiple_documentsHay más de un documento en el fichero.
side_missingFalta una cara.
language_mismatchEl idioma no es el esperado para el tipo.

Los warnings son indicios, no prueba de autenticidad.

Los avisos son señales, no pruebas de fraude. Constaia no es un KYC biométrico ni compara caras.

Paso 1: decide dónde está el documento para el revisor

Un revisor necesita ver el documento. Constaia no tiene un endpoint para descargar el fichero original que subiste (solo las exportaciones de los datos), así que tienes dos opciones:

  • Guardar tu propia copia solo mientras haga falta: al recibir el fichero, guárdalo cifrado en tu almacenamiento. Si el veredicto es valid o invalid, bórralo al momento; si es review, consérvalo hasta que el revisor decida.
  • No guardar nada y pedir otra foto: si no quieres custodiar documentos, en review pide directamente un documento nuevo (paso 5). Es la opción más simple cuando el motivo es solo low_quality.

Guardar el fichero en Constaia (storage: "temporary") no le sirve a tu revisor, porque no puede descargarlo. Si envías storage: "none", que es lo habitual, recuerda que la copia de referencia es la tuya.

Paso 2: recibir el review

En un análisis síncrono el review llega en la propia respuesta. Además, Constaia envía el webhook analysis.review_required:

OrigenEventos que recibes
Análisis individual (síncrono o async)analysis.completed y analysis.review_required
Documento de un loteSolo analysis.review_required (y batch.completed al final del lote)

Si procesas tanto la respuesta como los webhooks, crea el elemento de la cola con el id del análisis como clave única: así no se duplica. Usa metadata para saber a qué registro de tu sistema corresponde.

migrations/review_queue.sql
CREATE TABLE review_queue (
  analysis_id     text PRIMARY KEY,
  registration_id text NOT NULL,
  reasons         jsonb NOT NULL,
  warnings        jsonb NOT NULL,
  file_path       text,
  status          text NOT NULL DEFAULT 'pending',  -- pending | approved | rejected | new_document_requested
  reviewer        text,
  decided_at      timestamptz,
  note            text,
  created_at      timestamptz NOT NULL DEFAULT now()
);
webhooks.js
import express from "express";
import { Constaia, WebhookVerificationError } from "@constaia/sdk";
import { sql } from "./db.js";

const app = express();
const constaia = new Constaia();

app.post("/webhooks/constaia", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = await constaia.webhooks.verify(req.body, req.headers, process.env.CONSTAIA_WEBHOOK_SECRET);
  } catch (err) {
    if (err instanceof WebhookVerificationError) return res.status(400).send("invalid signature");
    throw err;
  }

  if (event.type === "analysis.review_required") {
    const analysis = event.data;
    await sql`
      INSERT INTO review_queue (analysis_id, registration_id, reasons, warnings)
      VALUES (${analysis.id}, ${analysis.metadata.registration_id},
              ${JSON.stringify(analysis.verdict.reasons.filter((r) => r.severity !== "info"))},
              ${JSON.stringify(analysis.warnings)})
      ON CONFLICT (analysis_id) DO NOTHING`;
  }

  res.sendStatus(200);
});

app.listen(3000);

Paso 3: enseñar el documento al revisor

En la pantalla de revisión muestra:

  • Tu copia del documento (paso 1).
  • Los motivos con warning: explican por qué no se decidió solo. Ya vienen en el idioma de language.
  • Los campos extraídos y su confidence, para que el revisor compare con la imagen. Si guardas resultados (keep_results: true, por defecto), léelos cuando los necesites con GET /v1/analyses/{id}; si no, guarda en la cola los campos que el revisor tenga que ver.
  • fields.<campo>.source.bbox (coordenadas normalizadas 0–1 en la página source.page), por si quieres resaltar dónde está cada dato en la imagen. Puede ser null.

Paso 4: registrar la decisión

La decisión humana se guarda en tu sistema: Constaia no tiene un endpoint para cambiar el veredicto de un análisis. Guarda quién decidió, cuándo, qué y por qué, y enlázalo con el analysis_id:

decision.sql
UPDATE review_queue
SET status = 'approved', reviewer = 'ana@club.example', decided_at = now(),
    note = 'DNI legible en la copia; datos coinciden con el formulario'
WHERE analysis_id = 'an_01J...';

Después aplica la consecuencia (confirmar la inscripción, rechazarla) y borra tu copia del documento. Si guardaste el fichero o los resultados en Constaia y ya no los necesitas, bórralos con DELETE /v1/analyses/{id}.

Paso 5: pedir una foto nueva

Cuando el motivo es de calidad (low_quality) o el revisor no puede leer el documento, pide otro. Envía a la persona un enlace a tu página de subida (con el widget, que avisa de fotos borrosas, oscuras o con poca resolución antes de enviar) y vuelve a analizar. El nuevo análisis tiene otro id: guarda la relación con el anterior y marca el elemento de la cola como new_document_requested.

Consejos que puedes mostrar según el aviso: blurry (sujeta el móvil firme y enfoca), glare (evita reflejos, sin flash), cropped (que se vean las cuatro esquinas), side_missing (sube las dos caras), screen_photo_suspected o photocopy_suspected (fotografía el documento original).

Próximamente: revisión humana gestionada por Constaia

Estamos preparando una revisión humana hecha por el equipo de Constaia para los documentos en review, con un coste de 0,40 € por documento. Todavía no está disponible: hoy la revisión la haces tú con este flujo. Consulta precios y el changelog.

Probarlo

Con una clave ck_test_…, una imagen llamada blurry.jpg (o que contenga blur o borros) devuelve el DNI de dni_valid con los avisos blurry y low_quality:

respuesta (extracto)
{
  "verdict": {
    "expected": ["es_dni"],
    "match": true,
    "status": "review",
    "reasons": [
      { "code": "type_match", "severity": "info", "message": "El documento es DNI (España)." },
      { "code": "not_expired", "severity": "info", "message": "Vigente hasta el 12/03/2031." },
      { "code": "low_quality", "severity": "warning", "message": "La calidad de la imagen es insuficiente (blurry, low_quality)." }
    ]
  },
  "warnings": ["blurry", "low_quality"]
}

Si la envías como análisis individual, recibirás analysis.completed y analysis.review_required; dentro de un lote, solo analysis.review_required. Desde el panel también puedes mandar un evento de prueba a tu endpoint. Más en Modo test.

Siguientes pasos

En esta página