Skip to main content

Flujo: Webhooks (notificaciones asíncronas)

Para qué sirven

Trébol procesa documentos en background. Los webhooks te notifican cuando algo termina (item completado, verificación finalizada, CURP encontrada, etc.) sin que tengas que hacer polling.

Crear un webhook

Body mínimo:
Respuesta 201:
⚠️ El secret solo se muestra una vez en esta respuesta. Guárdalo de inmediato en tu secret manager.

Tipos de eventos

verification.v2.created

Verificación creada en el sistema.

verification.v2.finished

Verificación completada exitosamente. Es el evento más importante — significa que ya puedes leer todos los resultados.

verification.v2.extraction_completed

Todos los items de extracción terminaron, pero la verificación aún no está marcada como “finished”. Útil para acceso anticipado a datos extraídos.

verification.v2.document_status_updated

Cambió el estado documental (documents_status) de la verificación: pending_uploadpartial_uploadpending_externalfull_upload (puede retroceder tras una reapertura). El payload incluye documents_status, previous_documents_status y updated_at. Para saber cuándo el prospecto completó su expediente, filtra por documents_status: "full_upload". Nota: este evento no incluye account_name.

verification.v2.findings.updated

Trébol recalculó los hallazgos de la Síntesis de Dictamen: lo que le falta al expediente o requiere atención. Se emite en cada corrida, incluidas las provisionales previas al finished. Es la primera vía por la que los hallazgos salen de Trébol. El payload trae findings[] (severity, message, missing_field?), source (provisional | final), computed_at y run_id. findings: [] es un resultado válido: la corrida no encontró nada. Nota: no trae account_name, ni status, ni verification_tag. status y el tag (como campo tag) los devuelve GET /verifications/{verification-id}; account_name no lo trae este evento ni ese endpoint (otros eventos v2 sí lo incluyen), así que tenlo de tu lado — account_id es tu propia cuenta. missing_field es una etiqueta descriptiva, no un catálogo cerrado, y no corresponde uno a uno con los item_type: muéstrala, no ramifiques lógica con ella. Solo se guarda la corrida más reciente, no un historial: si se agotan los reintentos, esa corrida no se recupera (consultar la verificación da la vigente, no la perdida). Persiste cada una solo si te importa la evolución; para el estado final basta leer la verificación.

verification_item.v2.completed

Un item específico completó su procesamiento. Contiene item_error si hubo problema. Códigos comunes:
  • Documentales: password_protected_pdf (PDF con contraseña), get_input_file_info_failed (falló al leer el archivo).
  • doc_splitter: unsupported_file_type, unknown_custom_item_type, misconfigured_custom_item_type, no_splits_returned, pdf_slice_failed, pdf_slice_upload_failed, doc_splitter_request_failed (ver detalle en la guía de doc_splitter).
  • doc_validation: invalid_document_type, ruleset_validation_failed.
Trata item_error como un string opaco: pueden llegar otros códigos específicos por item_type (por ejemplo prevalidation_failed en csf_mx).

verification_item.v2.internal_status_changed

Cambio de estado interno de un item.

verification_item.v2.extraction_completed

Item específico terminó su extracción (antes de marcarse completed). success: boolean indica si fue exitosa.

verification_people.curp_search_completed

Trébol terminó de buscar el CURP de una persona. Posibles errores:
  • curp_format_error — CURP mal formado
  • curp_scrapper_error — error al extraer info del servicio externo
  • curp_service_unavailable — servicio caído

Ejemplos de payload por evento

verification.v2.finished

verification.v2.document_status_updated (expediente completo)

A diferencia de los demás eventos v2, el payload no trae account_name.

verification.v2.findings.updated

findings: [] también es un payload válido: la corrida no encontró nada. Forma distinta al GET: en el webhook findings es el array y source/computed_at van a su lado; en GET /verifications/{verification-id} es un objeto y los hallazgos están en findings.items. run_id existe solo en el webhook. source: "final" implica verificación completada. Una corrida provisional se calcula antes de finalizar y corridas posteriores pueden reemplazarla. La final se calcula al finalizar y es la definitiva. Para quedarte con la vigente, aplica la de computed_at más reciente y descarta las anteriores. Si empatan, final gana sobre provisional. Una verificación reabierta puede emitir dos final: ahí también decide computed_at. Mismo run_id = reentrega, no recálculo. Disparador: se completa un item, con el expediente ya quieto y los hallazgos guardados viejos. Las corridas se agrupan (una por ráfaga, no una por documento). severity es enum cerrado (high|medium|low); missing_field no. La corrida final se emite antes que verification.v2.finished, pero las entregas pueden llegar fuera de orden: si quieres los hallazgos definitivos al cierre, espera source: "final".

verification_item.v2.completed (con error)

item_error es opcional. Si el item se procesó bien, no aparece.

verification_people.curp_search_completed (con error)

Validar la firma HMAC-SHA256

Cada webhook llega con header Trebol-Signature: t=1640995200,v1=abc123def456...
  • t= — timestamp Unix de cuándo se generó la firma
  • v1= — firma HMAC-SHA256
⚠️ Los headers HTTP son case-insensitive según el RFC. Express y la mayoría de frameworks normalizan a lowercase (req.headers['trebol-signature']). Si tu framework es case-sensitive, usa Trebol-Signature exactamente.

Pasos para validar

  1. Extraer t y v1 del header
  2. Construir el string a firmar: {timestamp}.{payload_raw}
  3. Calcular HMAC-SHA256 usando tu webhook secret
  4. Comparar con v1 usando comparación de tiempo constante (no ===)

Ejemplo Node.js

Ejemplo Python

IPs de origen

Trébol envía webhooks desde:
  • 35.170.236.123
  • 54.162.134.233
Puedes whitelistear estas IPs en tu firewall, pero siempre debes validar la firma HMAC. Las IPs pueden cambiar; la firma no falla.

Reintentos

Si tu endpoint no responde 2xx, Trébol reintenta con backoff exponencial: Después de 5 fallos, el webhook se marca como fallido y no se reintenta más. Trébol reintenta cuando:
  • Tu servidor responde 4xx o 5xx
  • Hay timeout de conexión
  • Error de red

Reglas de oro

1. Responde 200 OK antes de procesar

Valida la firma, encola el evento, responde. El procesamiento real va en un worker.

2. Implementa idempotencia con la firma del header

Los reintentos pueden duplicar eventos. Usa la firma v1= del header Trebol-Signature como clave de deduplicación, ya que es única por evento. Esto es lo que recomienda la guía oficial de webhooks.
Almacena los IDs procesados al menos 24 horas para manejar reintentos tardíos. Redis con TTL es ideal.

3. No asumas orden de eventos

Puedes recibir verification_item.v2.completed antes de verification.v2.created. Si necesitas estado actual, consulta el API.

4. Procesa con cola asíncrona

Usa RabbitMQ, SQS, Celery, BullMQ. Si haces todo síncrono, vas a tener timeouts en picos de tráfico.

5. TTL de dedupe ≥ 24 h

Guarda los IDs procesados al menos 24 horas para manejar reintentos tardíos. Redis es ideal.

6. Verifica el timestamp para evitar replay (opcional)

Rechaza webhooks con t= mayor a 5 minutos en el pasado:

Después de verification_people.curp_search_completed

Cuando recibas este webhook, puedes obtener los datos de CURP:
⚠️ Path param {verification-id} en kebab. El field verification_id (snake) está en el payload del webhook, lo conviertes en path al hacer la lectura. En la respuesta, busca la persona cuyo people_id coincida y lee external_identities.curp: