CPFHub.io

Webhook de la Verificación de Identidad

Cada decisión de una Verificación de Identidad, automática o tomada por tu equipo en el dashboard, genera un POST a la URL que registraste. El evento no trae datos personales: solo IDs, la decisión y los motivos.

Configurar

En el dashboard, en Verificações > Configurações > Integrações:

  1. Registra la URL de tu endpoint. Solo https y solo direcciones públicas.
  2. Copia el secreto y guárdalo en una variable de entorno (ej.: CPFHUB_WEBHOOK_SECRET).
  3. Usa el envío de prueba para recibir un evento kyc.test y comprobar la firma.

Evento

JSON
{
  "event": "kyc.decision",
  "eventId": "3e8a1f20-5c4b-4d9e-8f7a-6b2c1d0e9f8a",
  "verificationId": "8f3c2a1e-77b0-4d3c-9a1f-0e5b6c7d8e9f",
  "linkId": "5b1d7c2e-8a4f-4e7b-9c3d-2f6a1e0b9d47",
  "externalId": "cliente-4821",
  "status": "approved",
  "previousStatus": null,
  "decidedBy": "automatic",
  "reasons": [],
  "occurredAt": "2026-10-27T14:06:12.000Z"
}
CampoTipoDescripción
eventstringkyc.decision, o kyc.test en el envío de prueba
eventIdstringID del evento
verificationIdstringID de la verificación
linkIdstring | nullEl id devuelto por POST /kyc/links
externalIdstring | nullEl identificador que enviaste al crear el enlace
statusstringDecisión: approved, manual_review o rejected. Ver Decisión.
previousStatusstring | nullLa decisión anterior, cuando cambió (por ejemplo, de manual_review a approved)
decidedBystringautomatic (reglas de la cuenta) o manual (tu equipo en el dashboard)
reasonsarrayMotivos de la decisión automática: [{ "code", "message" }]. Vacío en decisiones manuales.
occurredAtstringCuándo se tomó la decisión (ISO 8601, UTC)

Encabezados

HeaderDescripción
x-cpfhub-eventEl mismo valor de event
x-cpfhub-deliveryID de la entrega. Úsalo para ignorar repeticiones.
x-cpfhub-signaturet=<unix>,v1=<hex>: momento del envío en segundos y la firma

Verificar la firma

v1 es el HMAC-SHA256, en hexadecimal, de "<t>.<cuerpo crudo>" con tu secreto. Calcúlalo sobre el cuerpo exactamente como llegó, antes de cualquier parseo de JSON, compara en tiempo constante y rechaza eventos con t a más de 5 minutos de tu reloj.

import crypto from 'node:crypto'
import express from 'express'

const app = express()

// Cuerpo crudo: la firma se calcula sobre los bytes recibidos
app.post('/webhooks/cpfhub', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('x-cpfhub-signature') ?? ''
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  const expected = crypto
    .createHmac('sha256', process.env.CPFHUB_WEBHOOK_SECRET!)
    .update(`${parts.t}.${req.body}`)
    .digest()
  const received = Buffer.from(parts.v1 ?? '', 'hex')

  const valid =
    received.length === expected.length &&
    crypto.timingSafeEqual(received, expected) &&
    Math.abs(Date.now() / 1000 - t) < 300
  if (!valid) return res.sendStatus(401)

  const event = JSON.parse(req.body.toString())
  if (event.event === 'kyc.decision') {
    // Vincula la decisión con tu registro por el externalId
    console.log(event.externalId, event.status)
  }
  res.sendStatus(200)
})

Entrega y reintentos

  • Confirmación: responde con cualquier status 2xx en hasta 10 segundos. Procesa el evento después (cola), no antes de responder.
  • Reintentos: sin 2xx, por timeout o redirección, volvemos a intentar en 1 min, 5 min, 30 min, 2 h y 12 h. Después de eso la entrega queda como fallida, y puedes reenviarla desde el dashboard.
  • Repeticiones: el mismo evento puede llegar más de una vez. Ignora un x-cpfhub-delivery que ya hayas procesado.
  • Orden: no hay garantía de orden entre eventos. Usa occurredAt y, ante la duda, confirma la decisión actual con GET /kyc/links/{id}.
  • Destino: solo https y solo direcciones públicas. Las redirecciones no se siguen.
⚠

No confíes en la redirección

El regreso de la persona a tu redirectUrl no prueba nada: puede abrir esa URL por su cuenta. Libera el registro solo después de un webhook con firma válida, o de la consulta por la API.


Actualizado el 27 de octubre de 2026