NexoID

Eventos de webhook

NexoID compara cada noche el padrón nuevo con el de ayer y avisa de lo que cambió en los RUC que vigilas. Un evento es una entrega HTTP firmada a tu endpoint.

🔒 Watch solo vigila RUC de tipo 20, personas jurídicas. El payload lleva el nombre, y para un RUC 10 eso sería identidad saliendo a una URL ajena: sin dni.read, sin finalidad declarada y fuera de nuestra auditoría. Un RUC de persona natural se rechaza al añadirlo, y lo dice.

Los eventos

EventoCampoCuándo llega
ruc.estado_cambiado estado El estado del contribuyente cambió: ACTIVO, BAJA DE OFICIO, SUSPENSIÓN TEMPORAL…
ruc.condicion_cambiada condicion La condición del domicilio cambió: HABIDO o NO HABIDO.
ruc.agente_retencion_cambiado es_agente_retencion Pasó a ser, o dejó de ser, agente de retención del IGV, según el padrón de SUNAT. SUNAT lo actualiza al designar o excluir agentes, unas pocas veces al año.
ruc.buen_contribuyente_cambiado es_buen_contribuyente Entró o salió de la relación de buenos contribuyentes de SUNAT, que se actualiza pocas veces al año.
ruc.nombre_cambiado nombre Cambió la razón social.
ruc.alta — El RUC aparece en el padrón por primera vez.
ruc.baja — El RUC dejó de aparecer en el padrón publicado.

Qué llega a tu endpoint

Un POST con este cuerpo. referencia es el identificador que tú le diste a ese RUC al añadirlo: vuelve aquí para que no tengas que cruzarlo contra tu base.

{
    "evento": "ruc.estado_cambiado",
    "ocurrido_en": "2026-09-19T03:14:07-05:00",
    "datos": {
        "ruc": "20131312955",
        "campo": "estado",
        "antes": "ACTIVO",
        "despues": "BAJA DE OFICIO",
        "fecha_dato": "2026-09-19"
    },
    "watchlist": {
        "id": 4,
        "nombre": "Proveedores",
        "referencia": "PROV-118"
    }
}
CabeceraPara qué
X-NexoID-EventEl nombre del evento, como en la tabla de arriba
X-NexoID-DeliveryId de la entrega, estable entre reintentos: es tu clave de idempotencia
X-NexoID-TimestampSegundos Unix. Va firmado: rechaza desfases de más de cinco minutos
X-NexoID-SignatureHMAC-SHA256 del timestamp y el cuerpo, con tu secreto
X-NexoID-TestSolo en la entrega de prueba. No cuenta para el deshabilitado automático

Verificar la firma

Firma el cuerpo tal y como llega. Si lo parseas y lo vuelves a serializar, un espacio distinto cambia el hash y la firma deja de coincidir. Guarda el cuerpo crudo antes de interpretarlo.

// El material firmado es «{timestamp}.{cuerpo}», con el cuerpo TAL CUAL llegó:
// si lo vuelves a serializar, la firma deja de coincidir.
const material = `${cabeceras['x-nexoid-timestamp']}.${cuerpoCrudo}`;
const esperado = 'sha256=' + createHmac('sha256', secreto).update(material).digest('hex');

// Comparación en tiempo constante: un `===` filtra por dónde empieza a fallar.
if (!timingSafeEqual(Buffer.from(esperado), Buffer.from(cabeceras['x-nexoid-signature']))) {
  return respuesta.status(401).end();
}

// Idempotencia: `X-NexoID-Delivery` es estable entre reintentos. Si ya
// procesaste ese id, responde 200 y no lo apliques dos veces.

Si tu endpoint falla

Una entrega que no responde 2xx se reintenta 6 veces, esperando 1 min, 5 min, 30 min, 2 h, 6 h y 24 h. Después se descarta esa entrega.

🚩 Tras 20 intentos fallidos seguidos el endpoint se deshabilita y deja de recibir. No es un castigo: un endpoint roto que sigue recibiendo acumula cola para todos. Lo verás en GET /id/v1/watch/webhooks, que devuelve activo, fallos_seguidos y desde cuándo. Para reactivarlo, escríbenos: primero conviene saber por qué se cayó.

Un éxito pone el contador a cero. Y la entrega de prueba —POST /id/v1/watch/webhooks/{id}/probar— nunca cuenta para deshabilitar: está para que puedas equivocarte mientras integras.