Ir al contenido

Webhooks de eventos en tiempo real

Los webhooks permiten que tu sistema reciba notificaciones HTTP automáticas cuando ocurren eventos importantes en Redcumbre. En lugar de consultar periódicamente nuestra API (polling), configuras una URL y nosotros te avisamos cuando algo sucede.


Tu Sistema Redcumbre Evento
│ │ │
│── Copia el secreto del panel ─│ │
│ (y, si quieres, una URL │ │
│ por defecto) │ │
│ │ │
│ │◀─────── Ocurre evento ─────│
│ │ (BHE emitida, etc) │
│ │ │
│◀── POST webhook ─────────────│ │
│ (JSON + firma HMAC) │ │
│ │ │
│── 200 OK ────────────────────▶│ │
│ │── Log: WEBHOOK_SENT │

Características:

  • Procesamiento asíncrono (no bloquea la operación principal)
  • Reintentos automáticos con backoff exponencial
  • Todo webhook va firmado con HMAC-SHA256, a cualquier destino
  • Headers personalizados que viajan a cualquier destino; tu receptor reconoce a rCAPI por la firma, no por ellos
  • Un historial de entregas con cada intento, en el panel

La configuración de webhooks de tu empresa se hace en el panel de administración de Redcumbre:

General → Webhooks, pestaña «Configuración». Si usas PINRUT desde su propio panel, es la pestaña Configuración → Webhooks de ese panel: es la misma configuración.

Es la configuración por defecto de tu empresa, y tiene dos bloques:

BloqueQué guardaA qué destinos aplica
Firma y configuración por defectoEl secreto de firma y los headers personalizadosA todos los webhooks de tu empresa, vayan a la URL por defecto o a la URL que declaró una operación
URL por defectoLa URL, el interruptor «Enviar a esta URL» y la lista de eventos habilitadosSólo a la URL por defecto

La configuración es por modo: el modo real y el modo de prueba tienen cada uno su secreto, sus headers y su URL por defecto. Un aviso de prueba nunca se firma con el secreto real, ni al revés.

La regla es una sola, y es la misma para todos los productos:

  1. Si la operación declaró su propia URL, el aviso va sólo a esa URL. No importa la lista de eventos habilitados ni el interruptor: declarar la URL en la solicitud ya es pedir el aviso. La URL por defecto no lo recibe. Hoy declaran su URL el callback_url de una transacción de PINRUT, la webhookUrl de una sesión de tokenización y la webhookUrl de una verificación OTP.
  2. Si no declaró ninguna, el aviso va a la URL por defecto sólo si existe, si «Enviar a esta URL» está encendido y si la lista de eventos habilitados lo deja pasar.
  3. Si no hay a dónde enviarlo, no se envía, y queda en el historial de entregas como «no enviada», con su motivo.

Para no recibir los avisos de una operación en una URL propia, no la declares en la solicitud. Para no recibirlos en la URL por defecto, quítalos de la lista de eventos habilitados o apaga «Enviar a esta URL».

El secreto es un valor de 64 caracteres hexadecimales con el que se firma cada webhook (HMAC-SHA256). Nace solo con el primer aviso que tu empresa tenga que enviar, así que no necesitas hacer nada para que tus webhooks salgan firmados; si quieres tenerlo antes, el botón «Generar secreto» lo crea sin configurar ninguna URL.

Lo copias del bloque «Firma y configuración por defecto»: con «Mostrar» lo ves y con el botón de copiar lo llevas a tu receptor. El secreto no se borra nunca; sólo se regenera (ver Regenerar el secreto).

Los headers del panel viajan en todos los webhooks de tu empresa, también a la URL que declaró una operación, aunque sea de otro dominio. Si la solicitud trae headers propios (por ejemplo, los webhookHeaders de una sesión de tokenización), van encima de los del panel y ganan cuando el nombre se repite. Ningún header personalizado reemplaza al de la firma.

La URL por defecto es opcional: puedes tener el secreto y los headers sin ella. Al guardar una URL donde no había ninguna, «Enviar a esta URL» se enciende solo. «Quitar URL» borra la URL y apaga el envío, y conserva el secreto, los headers y la lista de eventos.

CampoDescripción
URLURL HTTPS donde recibirás los avisos que no traen una URL propia
Enviar a esta URLEnciende o apaga el envío a la URL por defecto. No alcanza a la URL que declaró una operación
Eventos habilitadosQué eventos van a la URL por defecto. Dejarla vacía significa «todos», no «ninguno». No filtra lo que va a la URL que declaró una operación

Una verificación OTP con webhookUrl envía cada uno de sus eventos una sola vez, sólo a esa URL, firmado y con los headers del panel, sin mirar el interruptor ni la lista. Sin webhookUrl, el evento va a la URL por defecto según el interruptor y la lista. Nunca se envía a las dos.


EventoDescripción
tokenizacion.completadaEmisor autorizado exitosamente vía SII, al cerrarse una sesión de tokenización. No se emite al editar o crear un emisor fuera de una sesión
emisor.actualizadoUn emisor DTE o tributario cambió por el panel o por la API, fuera de una sesión de tokenización (edición, reactivación, u otro verbo que modifique sus datos). Opt-in estricto
emisor.creadoAlta directa de un emisor DTE o tributario, sin pasar por una sesión de tokenización. Opt-in estricto
emisor.certificadoUn emisor DTE quedó productivo ante el SII. Uno por carril
emisor.revocadoEmisor revocado (DTE o Tributario)
bhe.emitidaBoleta de Honorarios emitida al SII
bhe.emision_terminadaPDF SII disponible (modos SINC_PARCIAL/ASINCRONO)
bhe.pdf_sii_fallidoPDF SII falló después de 7 días de reintentos
bhe.emision_reintentandoUn intento de emisión asíncrona falló y va a reintentarse — informativo
bhe.emision_fallidaEmisión asíncrona falló definitivamente. Se envía una sola vez
bhe.anuladaAnulación de la BHE aceptada por el SII
bhe.anulacion_fallidaEl SII rechazó la anulación de la BHE
bhet.emitidaBoleta de Terceros emitida al SII
bhet.emision_terminadaPDF SII de la BHET disponible
bhet.pdf_fallidoGeneración del PDF SII de la BHET falló
bhet.emision_reintentandoUn intento de emisión asíncrona de BHET falló y va a reintentarse
bhet.emision_fallidaEmisión asíncrona de BHET falló definitivamente. Se envía una sola vez
bhet.anuladaAnulación de la BHET aceptada por el SII
bhet.anulacion_fallidaEl SII rechazó la anulación de la BHET
dte.emitidoEl documento existe: folio, timbre y XML firmado. Opt-in estricto
dte.emision_terminadaEl SII resolvió el documento. Opt-in estricto
dte.emision_sin_confirmarLa plataforma dejó de procurar el desenlace de un documento que ya existía sin obtener un veredicto del SII. No afirma rechazo ni pérdida del documento. Opt-in estricto
dte.emision_reintentandoUn intento de emisión encolada de DTE (respuesta con intentoId) falló y va a reintentarse
dte.emision_fallidaEmisión encolada de DTE (respuesta con intentoId) falló definitivamente. Se envía una sola vez
dte.guia_anuladaUna guía de despacho (52) quedó anulada (códigos 1 y 2 del Libro de Guías)
dte.guia_modificadaUna guía de despacho (52) vigente quedó modificada por recepción parcial (código 3)
dte.credencial_portal_rechazadaEl SII dejó de aceptar la credencial del portal del contribuyente. No detiene la emisión
dte.credencial_portal_restablecidaLa credencial del portal volvió a funcionar
bhe.credencial_rechazadaEl SII dejó de aceptar la credencial del emisor: la emisión BHE está detenida
bhe.credencial_restablecidaLa credencial volvió a funcionar y la emisión BHE se reanudó
bhet.credencial_rechazadaLo mismo, en el carril de boletas de terceros
bhet.credencial_restablecidaLa credencial volvió a funcionar y la emisión BHET se reanudó
dte.acuse_reclamadoDocumento reclamado por el receptor. El receptor reclamó el documento ante el SII (RCD, RFP o RFT): el reclamo bloquea la cesión y tiene efecto legal
dte.acuse_aceptadoDocumento aceptado por el receptor. El receptor otorgó recibo y aceptó el contenido del documento ante el SII (ERM, ACD)
otp.verification-createdVerificación OTP Creada. Verificación OTP creada y código enviado
otp.verification-deliveredVerificación OTP Entregada. Código OTP entregado al destinatario
otp.verification-approvedVerificación OTP Aprobada. Código OTP verificado correctamente
otp.verification-failedVerificación OTP Fallida. Verificación fallida por máximo de intentos o falla de entrega
otp.verification-expiredVerificación OTP Expirada. Verificación expirada por TTL cumplido
otp.verification-canceledVerificación OTP Cancelada. Verificación cancelada manualmente o supersedida
pin.transaction.authorizedTransacción PIN Autorizada. Verificación de identidad PIN completada exitosamente
pin.transaction.failedTransacción PIN Fallida. Verificación PIN fallida por PIN incorrecto o rate limit
pin.transaction.expiredTransacción PIN Expirada. Transacción PIN expirada sin completarse. Informa el motivo en expiration_reason: not_started (la persona no empezó: nunca abrió el enlace, o abrió un alta y no consintió) o not_completed (empezó y no terminó)
pin.transaction.cancelledTransacción PIN Cancelada. Transacción PIN cancelada por integrador
whatsapp.canal.transferidoNúmero de WhatsApp Transferido. El número de WhatsApp dejó de pertenecer a este tenant: otro lo conectó con autorización de Meta
whatsapp.mensaje.entranteMensaje de WhatsApp Recibido. Una persona escribió al número de WhatsApp del tenant. Los mensajes que no son de texto llegan sin su contenido
whatsapp.mensaje.ecoMensaje de WhatsApp Enviado desde la App. El negocio envió un mensaje desde WhatsApp Business App con el número conectado en coexistencia. Los mensajes que no son de texto llegan sin su contenido
whatsapp.mensaje.estadoEstado de un Mensaje de WhatsApp Despachado. Meta informó que un mensaje que despachaste se envió, se entregó o falló. Llega al webhook del canal sólo si activas la notificación de estados en ese canal

Emitir un documento y que el SII lo dé por bueno son dos cosas distintas, y cada una tiene su evento. Sirven para cualquier tipo de documento —facturas, boletas, notas, guías— y para cualquier forma de emisión.

HitoEventoQué afirma
El documento existedte.emitidoTiene folio del CAF, timbre y XML firmado. Sale al emitir, en toda forma de emisión, antes de que el SII responda
El SII lo resolviódte.emision_terminadaEl SII lo aceptó, lo aceptó con reparos o lo rechazó. Con resultado aceptado es el momento de entregarle el documento a tu receptor

Cuánto se separan en el tiempo depende de cómo se emite:

Forma de emisiónQué pasa entre un hito y el otro
Portal MiPyme (facturas, notas, guías)El portal devuelve el documento timbrado en el acto; el veredicto del registro de reclamos llega después
Boletas electrónicas (39 y 41)El envío al SII va en lotes, cada un minuto, y su respuesta dispara el segundo hito
XML propio (Full DTE)Un proceso envía el documento al SII y consulta el resultado por su track id
Integración externaSi delegaste el seguimiento ante el SII a tu propio sistema, el segundo hito no se emite: la plataforma no tiene veredicto que comunicarte

dte.emision_terminada se emite una sola vez por documento, en el momento en que el SII lo resuelve. Si más adelante ese documento cambia —por ejemplo, porque le emites una nota de crédito— no vuelve a dispararse.

No hay eventos propios de boleta ni de nota de crédito: una boleta o una nota pasan por los mismos dos hitos que una factura, y ramificas por tipoDte.

Hay un tercer desenlace observable, que no es ninguno de los dos hitos: a veces la plataforma deja de intentar confirmar el resultado de un documento que ya existía sin que el SII haya dicho nada. Puede pasarle a una factura en firma local o a una boleta. Para eso existe dte.emision_sin_confirmar.

Cuánto tardan las boletas. La emisión en sí es sincrónica: la respuesta HTTP ya te devuelve el folio del CAF y el documento firmado. Lo que ocurre después en background es el envío al SII —en lotes, cada un minuto— y el registro de su respuesta, que es lo que dispara el webhook. Medido sobre 10.687 boletas reales de producción (30 días, agosto de 2026): mediana 1 min 24 s, y el 99 % bajo 2 minutos desde que se emite la boleta hasta que sale el evento.

EventoDescripción
batch.completadoProceso batch terminado (exitoso o con errores). Opt-in estricto, igual que los de boleta

EventoDescripción
solicitud.creadaSe creó una solicitud en un formulario de Comunicación, entrara por la puerta que entrara
solicitud.resueltaEl equipo cerró la solicitud. Trae el motivo
solicitud.reabiertaUna solicitud cerrada se volvió a abrir

Los tres traen el número, el tipo, el estado, la puerta y la referencia que mandó tu sistema, y ningún dato de la persona. El cuerpo y cuándo sale cada uno están en Formularios de Comunicación.


EventoDescripción
kyc.identity.level_changedSe aprobó la revisión de identidad de la persona de una transacción de tu integración. Trae el transaction_id y en qué quedó la transacción (pending, authorized o closed). Alcanza sólo a la integración de esa transacción — ver Identidad Acreditada

Se envía cuando un usuario completa el flujo de autorización de credenciales SII.

{
"event": "tokenizacion.completada",
"tenantId": "8",
"sessionId": "c6c53392-bdb0-4053-8b9d-5b34296d63a3",
"code": "83aK0esV0V21u8-krvuMiXx1OqheSVy56E6tBG3OjX8",
"emisorId": "cmikf6ei00001sek0mxzcf668",
"rut": "77438768-4",
"tipoAutenticacion": "CLAVE_TRIBUTARIA",
"tipoEmisor": "tributario",
"correlationId": "mi-referencia-123",
"lookupData": {
"razonSocial": "EMPRESA EJEMPLO SPA",
"giro": "SERVICIOS INFORMATICOS",
"direcciones": [
{
"ciudad": "SANTIAGO",
"comuna": "PROVIDENCIA",
"direccion": "AV. PROVIDENCIA 1234"
}
]
},
"timestamp": "2025-12-10T15:30:00Z"
}

Cuando la sesión es tipoEmisor: "dte", el payload trae además emisores con todos los emisores que creó la sesión:

{
"event": "tokenizacion.completada",
"emisorId": "cmikf6ei00001sek0mxzcf668",
"tipoEmisor": "dte",
"emisores": [
{
"emisorId": "cmikf6ei00001sek0mxzcf668",
"carril": "FULL_DTE",
"estado": "ESPERA_CERTIFICACION",
"motivoInactivo": "PENDIENTE_CERTIFICACION",
"tiposHabilitados": []
},
{
"emisorId": "cmr8b52zn000fseodrcbqvr62",
"carril": "SOLO_BOLETA",
"estado": "ESPERA_CERTIFICACION",
"motivoInactivo": "PENDIENTE_CERTIFICACION",
"tiposHabilitados": []
}
]
}
CampoDescripción
emisorId (raíz)Apunta a un emisor: el de facturación si la sesión pidió uno; si fue sólo boletas, el de boletas
emisores[].carrilPORTAL_MIPYME o FULL_DTE (facturación) · SOLO_BOLETA (boletas)
emisores[].estadoOPERATIVO (puede emitir ya) o ESPERA_CERTIFICACION (nació inactivo, espera al SII)
emisores[].motivoInactivoPENDIENTE_CERTIFICACION, o null cuando el emisor está operativo
emisores[].tiposHabilitadosCódigos de tipo DTE que ese emisor puede emitir hoy. Qué significa depende del carril: ver abajo

tiposHabilitados por carril:

  • PORTAL_MIPYME: los tipos que el SII tiene autorizados y vigentes para ese RUT, entre los que se emiten por el Portal MiPyme (33, 34, 43, 46, 52, 56 y 61). Se leen del SII al momento de la autorización, así que no es la misma lista para todas las empresas: una puede tener 33, 34, 52, 56 y 61 y no tener ni el 43 ni el 46. Las boletas 39 y 41 del sistema gratuito del SII no entran acá: se emiten por SOLO_BOLETA.
  • FULL_DTE y SOLO_BOLETA: los tipos con que el emisor quedó certificado. Vacío mientras espera la certificación.

Una lista vacía significa que hoy ese emisor no puede emitir ningún tipo. Si el SII le autoriza o le quita un tipo después de la autorización, actualiza el dato con POST /:tenantSlug/emisores/:id/sincronizar.


Se envía cuando un emisor DTE queda productivo ante el SII: su certificación llegó al desenlace y el emisor puede emitir.

{
"event": "emisor.certificado",
"data": {
"emisorId": "cmikf6ei00001sek0mxzcf668",
"emisorType": "dte",
"rut": "77438768-4",
"razonSocial": "EMPRESA EJEMPLO SPA",
"tenantId": "8",
"solicitudId": "cmr9x2k1p0001se7ab3cd4efg",
"tipoIntegracion": "FULL_DTE",
"tiposHabilitados": [33, 34, 52, 56, 61]
},
"timestamp": "2026-01-15T11:02:44.318Z"
}
CampoDescripción
tipoIntegracionEl carril que quedó certificado: FULL_DTE o SOLO_BOLETA. Es el discriminante: no lo deduzcas de tiposHabilitados
tiposHabilitadosCódigos de tipo DTE que el emisor puede emitir a partir de ahora
solicitudIdLa solicitud de certificación que llegó al desenlace

Para operar en nombre de tu cliente, rCAPI entra al portal del SII con la credencial del contribuyente. Cuando el SII deja de aceptarla —la cambió, se la bloquearon, el certificado venció— las operaciones que dependen de ella se detienen, y el correo que sale va a la empresa: tu sistema no lo lee. Estos seis eventos son ese aviso, para tu sistema.

Son dos hechos —rechazo y restablecimiento— en tres carriles: DTE, boletas de honorarios y boletas de honorarios de terceros.

EventoCuándo
dte.credencial_portal_rechazadaEl SII rechazó la credencial del portal del contribuyente
dte.credencial_portal_restablecidaVolvió a aceptarla
bhe.credencial_rechazadaEl SII rechazó la credencial del emisor tributario, carril BHE
bhe.credencial_restablecidaVolvió a aceptarla
bhet.credencial_rechazadaLo mismo, carril BHET
bhet.credencial_restablecidaVolvió a aceptarla
{
"event": "dte.credencial_portal_rechazada",
"data": {
"emisorId": "cmikf6ei00001sek0mxzcf668",
"emisorType": "dte",
"rut": "77438768-4",
"razonSocial": "EMPRESA EJEMPLO SPA",
"tenantId": "8",
"motivo": "El certificado digital no se encuentra habilitado para este RUT",
"desde": "2026-01-15T11:02:44.318Z",
"queHacer": "Corrige la credencial en el portal del SII y vuelve a probar la conexión desde rCAPI para reanudar las operaciones detenidas.",
"titularNombre": "ANA PEREZ",
"titularRut": "11111111-1"
},
"timestamp": "2026-01-15T11:02:44.318Z"
}
CampoDescripción
motivoLo que dijo el SII. null cuando rechazó sin dar una explicación reconocible: no inventamos una causa
desdeDesde cuándo la credencial está rechazada
queHacerTexto listo para mostrarle a tu cliente
titularNombre, titularRutQuién tiene que corregirla en el SII. En DTE es el representante legal dueño del certificado; en bhe./bhet. coincide con el emisor
{
"event": "dte.credencial_portal_restablecida",
"data": {
"emisorId": "cmikf6ei00001sek0mxzcf668",
"emisorType": "dte",
"rut": "77438768-4",
"razonSocial": "EMPRESA EJEMPLO SPA",
"tenantId": "8",
"restablecidaEn": "2026-01-16T09:31:02.771Z"
},
"timestamp": "2026-01-16T09:31:02.771Z"
}

El restablecimiento no lleva motivo, desde ni los datos del titular: ya no hay nada que hacer, y el emisor va identificado por emisorId, rut y razonSocial.

  • Uno por emisor afectado. En DTE la credencial es del certificado del representante legal, y varios emisores pueden compartirla: si tres emisores tuyos la usan, recibes tres eventos.
  • Una sola vez por episodio. Los reintentos fallidos no vuelven a avisarte. Un episodio nuevo, después de restablecida, sí emite sus propios eventos.
  • Los dos carriles. Un emisor tributario con BHE y BHET habilitados recibe dos eventos, uno por carril.

Cuando tu cliente te avise que ya la corrigió en el SII, dispara el reintento por API:

  • DTE: POST /:tenantSlug/emisores/:id/credencial-portal/reintentar
  • BHE/BHET: POST /:tenantSlug/emisores-tributarios/:id/credencial/reintentar

Las dos están documentadas en la guía de emisores.


Se envía cuando un emisor es revocado (por el usuario o administrador).

{
"event": "emisor.revocado",
"data": {
"emisorId": "cmikf6ei00001sek0mxzcf668",
"emisorType": "tributario",
"rut": "77438768-4",
"razonSocial": "EMPRESA EJEMPLO SPA",
"tenantId": "8",
"revokedBy": "USER",
"motivoRevocacion": null
},
"timestamp": "2025-12-10T15:30:00Z"
}
CampoDescripción
emisorTypetributario o dte
revokedByUSER (usuario final) o ADMIN (administrador)
motivoRevocacionMotivo de revocación (solo cuando revokedBy: ADMIN)

Se envía cuando un emisor DTE o tributario cambia fuera de una sesión de tokenización: una edición por PATCH, una reactivación, o un verbo que modifica alguno de sus campos (marcar como predeterminado, sincronizar con el SII, resolver conflictos, subir o quitar el logo). No se envía si nada de lo que se declaró cambió realmente — por ejemplo, un PATCH con los mismos valores que ya tenía el emisor. Opt-in estricto: con la lista de eventos vacía, no lo recibes (ver arriba).

Dar de baja (DELETE) o revocar (PATCH :id/revoke) un emisor no emite este evento: la revocación ya tiene su propio aviso, emisor.revocado.

Es un cuerpo plano, sin el envoltorio data que usan emisor.revocado y emisor.certificado.

{
"event": "emisor.actualizado",
"tenantId": "8",
"emisorId": "cmikf6ei00001sek0mxzcf668",
"tipoEmisor": "dte",
"rut": "77438768-4",
"razonSocial": "EMPRESA EJEMPLO SPA",
"operation": "UPDATE",
"camposModificados": ["giroGlosa"],
"origen": "TENANT",
"timestamp": "2026-01-15T11:02:44.318Z",
"sandbox": false
}
CampoDescripción
tipoEmisordte o tributario
operationUPDATE en una edición normal; REACTIVATE cuando el emisor estaba inactivo o revocado y la misma operación lo deja activo (incluida una reactivación implícita, por ejemplo al cambiar el representante legal de un emisor revocado)
camposModificadosLos nombres de los campos que cambiaron de verdad, comparando el estado antes y después. Nunca sus valores: para saber a qué quedó un campo, consulta el emisor por la API
origenPLATAFORMA si el cambio lo hizo un usuario MASTER o un reseller operando la cuenta; TENANT si lo hizo un usuario del propio tenant o una llave de API
sandboxtrue si el cambio ocurrió en modo de prueba, false en modo real. Viene siempre, como en todo webhook

El evento nunca trae email, credenciales, certificado ni los datos de lookupData que sí trae tokenizacion.completada.


Se envía al dar de alta un emisor DTE o tributario directamente — sin pasar por una sesión de tokenización. Opt-in estricto: con la lista de eventos vacía, no lo recibes.

{
"event": "emisor.creado",
"tenantId": "8",
"emisorId": "cmikf6ei00001sek0mxzcf668",
"tipoEmisor": "tributario",
"rut": "77438768-4",
"razonSocial": "EMPRESA EJEMPLO SPA",
"operation": "CREATE",
"camposModificados": [],
"origen": "TENANT",
"timestamp": "2026-01-15T11:02:44.318Z",
"sandbox": false
}

operation siempre es CREATE y camposModificados siempre viene vacío — es un alta, no un diff. Los demás campos son los mismos de emisor.actualizado.


Se envía inmediatamente después de emitir una BHE exitosamente.

{
"event": "bhe.emitida",
"tenantId": "8",
"bheId": "cm5abc123",
"folioSii": "12345678",
"emisor": {
"rut": "78012039-8",
"razonSocial": "JUAN PÉREZ GONZÁLEZ"
},
"destinatario": {
"rut": "12345678-9",
"nombre": "EMPRESA CLIENTE SA",
"sinDestinatario": false
},
"montos": {
"bruto": 210000,
"ppm": 32025,
"liquido": 177975
},
"tipoRetencion": "RETRECEPTOR",
"canalEmision": "API_SYNC",
"correlationId": "mi-referencia-123",
"sandbox": false,
"estadoPdfSii": "PENDIENTE",
"descargas": {
"pdfInterno": "https://app.redcumbre.cl/acme/tributario/boletas-honorarios/cm5abc123/descargar?tipo=interno",
"pdfSii": "https://app.redcumbre.cl/acme/tributario/boletas-honorarios/cm5abc123/descargar?tipo=sii"
},
"timestamp": "2025-12-10T15:30:00Z"
}

descargas son los enlaces de descarga para usuarios con sesión en Redcumbre — los mismos que devuelve la respuesta de emisión. Ver Enlaces de descarga para tus usuarios.

Los montos del ejemplo están calculados con la tasa de PPM vigente en 2026 (15,25%). La tasa cambia por año tributario: no la deduzcas de este ejemplo ni la fijes en tu código — léela de GET /global/ppm, o toma ppm del propio evento, que es el que se retuvo en esa boleta.


Se envía cuando el PDF SII está disponible (modos SINC_PARCIAL y ASINCRONO).

{
"event": "bhe.emision_terminada",
"tenantId": "8",
"bheId": "cm5abc123",
"folioSii": "12345678",
"correlationId": "mi-referencia-123",
"pdfInternoUrl": "https://app.redcumbre.cl/p/boleta/eyJhbGciOiJIUzI1NiJ9...",
"pdfSiiUrl": "https://app.redcumbre.cl/p/boleta/eyJhbGciOiJIUzI1NiJ9...",
"estadoPdfSii": "DISPONIBLE",
"descargas": {
"pdfInterno": "https://app.redcumbre.cl/acme/tributario/boletas-honorarios/cm5abc123/descargar?tipo=interno",
"pdfSii": "https://app.redcumbre.cl/acme/tributario/boletas-honorarios/cm5abc123/descargar?tipo=sii"
},
"timestamp": "2025-12-10T15:35:00Z"
}

Se envía después de 7 días de reintentos fallidos para descargar el PDF SII.

{
"event": "bhe.pdf_sii_fallido",
"tenantId": "8",
"bheId": "cm5abc123",
"folioSii": "12345678",
"correlationId": "mi-referencia-123",
"intentosRealizados": 30,
"primerIntento": "2025-12-01T15:30:00Z",
"ultimoIntento": "2025-12-08T15:30:00Z",
"ultimoError": "SII_PDF_NO_DISPONIBLE",
"descargas": {
"pdfInterno": "https://app.redcumbre.cl/acme/tributario/boletas-honorarios/cm5abc123/descargar?tipo=interno"
},
"timestamp": "2025-12-08T15:30:00Z"
}

descargas trae solo pdfInterno: el respaldo SII no llegó a existir, así que no hay enlace que entregar para él.


Un intento de emisión asíncrona falló y el sistema va a volver a intentarlo. Es informativo: no tienes que hacer nada.

No reemitas al recibirlo. Si lo haces, terminas con dos boletas ante el SII: la tuya y la que el reintento original emite después.

{
"event": "bhe.emision_reintentando",
"tenantId": "8",
"correlationId": "mi-referencia-123",
"intentoActual": 3,
"intentosMaximos": 10,
"proximoIntentoEn": "2025-12-08T10:35:00Z",
"primerIntento": "2025-12-08T10:00:00Z",
"ultimoError": "SII_SERVICE_UNAVAILABLE",
"timestamp": "2025-12-08T10:15:00Z"
}

proximoIntentoEn es el instante real del próximo intento, no una estimación: puedes agendar contra ese timestamp.

intentosMaximos sale de la configuración del job que está corriendo, no de una constante global. Un job encolado antes de un cambio de política reporta el techo con el que fue encolado, así que el contador siempre cierra contra la realidad de ese intento.


La emisión asíncrona falló definitivamente. Este evento llega una sola vez por emisión, y sólo en dos casos:

  • se agotaron los 10 intentos de la ventana de reintentos (18–22,6 h), o
  • el SII devolvió un error que no se puede recuperar reintentando (contribuyente no habilitado, RUT inválido, certificado vencido), en cuyo caso llega en el primer intento.

Es la señal ante la cual sí corresponde actuar. Mientras la emisión sigue reintentándose recibes bhe.emision_reintentando.

{
"event": "bhe.emision_fallida",
"tenantId": "8",
"correlationId": "mi-referencia-123",
"intentosRealizados": 10,
"primerIntento": "2025-12-08T10:00:00Z",
"ultimoIntento": "2025-12-09T08:36:00Z",
"ultimoError": "SII_SERVICE_UNAVAILABLE",
"timestamp": "2025-12-09T08:36:00Z"
}

Se envía inmediatamente después de emitir una Boleta de Terceros exitosamente.

{
"event": "bhet.emitida",
"tenantId": "8",
"bhetId": "cm5xyz789",
"folioSii": "462",
"emisor": {
"rut": "76123456-7",
"razonSocial": "EMPRESA CONTRATANTE SPA"
},
"tercero": {
"rut": "78012039-8",
"nombre": "FIRERAISE SPA",
"domicilio": "AV PROVIDENCIA 1234 OF 501",
"comuna": "SANTIAGO"
},
"montos": {
"bruto": 150000,
"impuesto": 22875,
"neto": 127125
},
"canalEmision": "API_SYNC",
"correlationId": "mi-referencia-123",
"sandbox": false,
"estadoPdfSii": "PENDIENTE",
"descargas": {
"pdfInterno": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5xyz789/descargar?tipo=interno",
"pdfSii": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5xyz789/descargar?tipo=sii"
},
"timestamp": "2025-12-10T15:30:00Z"
}

En BHET el respaldo SII se genera en background, así que este evento siempre llega con estadoPdfSii: PENDIENTE. Ver Enlaces de descarga para tus usuarios.

Los montos del ejemplo están calculados con la tasa de retención vigente en 2026 (15,25%). La tasa cambia por año tributario: no la deduzcas de este ejemplo ni la fijes en tu código — léela de GET /global/bhet-retencion, o toma impuesto del propio evento, que es el que se retuvo en esa boleta.


Se envía cuando el PDF SII de la BHET quedó disponible.

{
"event": "bhet.emision_terminada",
"tenantId": "8",
"bhetId": "cm5xyz789",
"folioSii": "462",
"correlationId": "mi-referencia-123",
"pdfInternoUrl": "https://app.redcumbre.cl/p/boleta/eyJhbGciOiJIUzI1NiJ9...",
"pdfSiiUrl": "https://app.redcumbre.cl/p/boleta/eyJhbGciOiJIUzI1NiJ9...",
"estadoPdfSii": "DISPONIBLE",
"descargas": {
"pdfInterno": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5xyz789/descargar?tipo=interno",
"pdfSii": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5xyz789/descargar?tipo=sii"
},
"timestamp": "2025-12-10T15:35:00Z"
}

Se envía cuando falla la generación del PDF SII de la BHET.

{
"event": "bhet.pdf_fallido",
"tenantId": "8",
"bhetId": "cm5xyz789",
"folioSii": "462",
"correlationId": "mi-referencia-123",
"error": "Timeout al convertir el HTML del SII a PDF",
"descargas": {
"pdfInterno": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5xyz789/descargar?tipo=interno"
},
"timestamp": "2025-12-10T15:40:00Z"
}

La boleta está emitida ante el SII: lo que falló es el respaldo en PDF. Por eso descargas trae solo pdfInterno.


Igual que bhe.emision_reintentando, para boletas de terceros: un intento falló y el sistema va a volver a intentarlo. Informativo, no reemitas.

{
"event": "bhet.emision_reintentando",
"tenantId": "8",
"correlationId": "mi-referencia-123",
"intentoActual": 3,
"intentosMaximos": 10,
"proximoIntentoEn": "2025-12-08T10:35:00Z",
"primerIntento": "2025-12-08T10:00:00Z",
"ultimoError": "SII_SERVICE_UNAVAILABLE",
"timestamp": "2025-12-08T10:15:00Z"
}

Mismos campos que bhe.emision_reintentando: proximoIntentoEn es el instante real del próximo intento, e intentosMaximos sale del job en curso.


La emisión asíncrona de la BHET falló definitivamente: se agotaron los 10 intentos, o el error del SII no se puede recuperar reintentando. Llega una sola vez por emisión.

{
"event": "bhet.emision_fallida",
"tenantId": "8",
"correlationId": "mi-referencia-123",
"intentosRealizados": 10,
"primerIntento": "2025-12-08T10:00:00Z",
"ultimoIntento": "2025-12-09T08:36:00Z",
"ultimoError": "SII_SERVICE_UNAVAILABLE",
"timestamp": "2025-12-09T08:36:00Z"
}

No incluye descargas ni bhetId: no hay boleta emitida.


Un intento de emisión asíncrona de un DTE —factura, boleta electrónica, nota de crédito— falló y el sistema va a volver a intentarlo. Informativo.

Sólo llega cuando la emisión quedó encolada: la respuesta trajo intentoId en vez de dte, que pasa únicamente con un emisor de Portal MiPyme y modo: "ASINCRONO". Con firma local (FULL_DTE) la respuesta dice modo: "ASINCRONO" pero el documento ya viene emitido con su folio: lo que queda en segundo plano es el envío al SII, y su desenlace llega en dte.emision_terminada o, si la plataforma no logra un veredicto del SII, en dte.emision_sin_confirmar. Nunca acá. Ver Modos de emisión.

No reemitas al recibirlo. Acá cuesta más caro que en boletas de honorarios: una reemisión consume un folio de tu CAF, que es un rango finito autorizado por el SII y que después hay que volver a pedir.

{
"event": "dte.emision_reintentando",
"tenantId": "8",
"correlationId": "mi-referencia-123",
"intentoActual": 3,
"intentosMaximos": 10,
"proximoIntentoEn": "2025-12-08T10:35:00Z",
"primerIntento": "2025-12-08T10:00:00Z",
"ultimoError": "SII_SERVICE_UNAVAILABLE",
"timestamp": "2025-12-08T10:15:00Z"
}

Mismos campos que bhe.emision_reintentando: proximoIntentoEn es el instante real del próximo intento, e intentosMaximos sale del job en curso.


La emisión asíncrona del DTE falló definitivamente: se agotaron los 10 intentos de la ventana de reintentos (18–22,6 h), o el error del SII no se puede recuperar reintentando (CAF vencido, folio duplicado, verificación de actividades pendiente). Llega una sola vez por emisión.

Ningún folio quedó consumido: el documento nunca llegó a emitirse.

Igual que dte.emision_reintentando, sólo aplica a una emisión encolada (respuesta con intentoId). Con firma local (FULL_DTE) el documento ya existe cuando recibes la respuesta, así que este evento nunca llega: un rechazo del SII te lo informa dte.emision_terminada con resultado, y una falla sin veredicto del SII, dte.emision_sin_confirmar.

{
"event": "dte.emision_fallida",
"tenantId": "8",
"correlationId": "mi-referencia-123",
"intentosRealizados": 10,
"primerIntento": "2025-12-08T10:00:00Z",
"ultimoIntento": "2025-12-09T08:36:00Z",
"ultimoError": "SII_SERVICE_UNAVAILABLE",
"timestamp": "2025-12-09T08:36:00Z"
}

El documento existe: tiene folio del CAF, timbre y XML firmado. Llega para cualquier tipo de documento y cualquier forma de emisión.

No afirma nada sobre el SII. Si tu documento se somete a revisión —que es casi siempre—, el veredicto llega después en dte.emision_terminada. Para entregarle el documento a tu receptor, espera dte.emision_terminada aceptado: un documento que el SII rechaza no le sirve al receptor.

{
"event": "dte.emitido",
"tenantId": "8",
"timestamp": "2026-08-20T12:30:00.000Z",
"data": {
"dteId": "dte_a1b2c3",
"tipoDte": "FACTURA_AFECTA",
"folio": "4321",
"rutEmisor": "76123456-7",
"rutReceptor": "77999888-1",
"razonSocialReceptor": "Cliente SpA",
"fechaEmision": "2026-08-20",
"montoTotal": "119000",
"moneda": "CLP"
}
}

El SII resolvió el documento. Es el evento que cierra el ciclo, y llega una sola vez por documento.

resultado es lo que tienes que mirar: son tres valores y no cambian nunca.

resultadoQué significa
ACEPTADOEl documento tiene validez tributaria
ACEPTADO_CON_REPAROSVálido igual, pero el SII acusó observaciones. reparos trae el detalle
RECHAZADOEl SII no lo aceptó. El documento no tiene validez tributaria

En firma local (Full DTE), RECHAZADO incluye también el rechazo del sobre completo que contenía el documento —por ejemplo, un error de esquema o de firma del envío—, no sólo el rechazo del documento individual. El resultado es el mismo RECHAZADO; el campo rechazo (más abajo) te dice cuál de los dos fue.

estadoSii viaja al lado con la sigla cruda que el SII devolvió —DOK, ACEPTADO, RPR, RCH…—. No la uses para decidir: el SII usa vocabularios distintos según el tipo de documento y por dónde se envió, así que la misma situación llega con siglas diferentes. Está para que puedas registrarla y para soporte.

{
"event": "dte.emision_terminada",
"tenantId": "8",
"timestamp": "2026-08-20T12:31:24.000Z",
"data": {
"dteId": "dte_a1b2c3",
"tipoDte": 33,
"folio": 4321,
"resultado": "ACEPTADO",
"estadoSii": "ACEPTADO",
"rutEmisor": "76123456-7",
"rutReceptor": "77999888-1",
"razonSocialReceptor": "Cliente SpA",
"montoTotal": 119000,
"reparos": null
}
}

Con reparos, reparos trae lo que observó el SII:

{
"event": "dte.emision_terminada",
"tenantId": "8",
"timestamp": "2026-08-20T12:31:24.000Z",
"data": {
"dteId": "dte_a1b2c3",
"tipoDte": 39,
"folio": 861310,
"resultado": "ACEPTADO_CON_REPAROS",
"estadoSii": "RLV",
"reparos": [
{ "codigo": "2", "descripcion": "Giro del emisor no corresponde" }
]
}
}

Cuando resultado es RECHAZADO y la plataforma conoce la respuesta cruda del SII, el payload trae además rechazo:

{
"event": "dte.emision_terminada",
"tenantId": "8",
"timestamp": "2026-08-20T12:31:24.000Z",
"data": {
"dteId": "dte_a1b2c3",
"tipoDte": 33,
"folio": 4321,
"resultado": "RECHAZADO",
"estadoSii": "RECHAZADO_SII",
"rutEmisor": "76123456-7",
"rutReceptor": "77999888-1",
"razonSocialReceptor": "Cliente SpA",
"montoTotal": 119000,
"rechazo": { "codigo": "RCT", "glosa": "Error en Carátula", "alcance": "ENVIO" }
}
}
Campo de rechazoQué significa
codigoLa sigla cruda que devolvió el SII
glosaEl texto del SII, o null si no dio uno reconocible
alcanceENVIO: el SII rechazó el sobre completo (en firma local un sobre puede traer varios documentos; ninguno quedó aceptado). DOCUMENTO: el SII rechazó este documento en particular

rechazo es null cuando resultado no es RECHAZADO, o cuando lo es pero el camino que lo detectó no trae la respuesta cruda del SII.

Cuando el SII rechaza una boleta electrónica, además del webhook la plataforma avisa por email a los usuarios con rol SUPER-ADMIN y ADMIN del tenant.


La plataforma dejó de procurar el desenlace ante el SII de un documento que ya existía —una factura en firma local o una boleta— sin llegar a un veredicto. Un documento que falló antes de firmarse no llegó a existir y no lo dispara. No afirma que el SII lo haya rechazado ni que no lo haya recibido — sólo que, desde este lado, no se pudo confirmar. Opt-in estricto: con «Eventos habilitados» vacío no lo recibes.

{
"event": "dte.emision_sin_confirmar",
"tenantId": "8",
"timestamp": "2026-08-20T12:31:24.000Z",
"data": {
"dteId": "dte_a1b2c3",
"tipoDte": 33,
"folio": 4321,
"estadoSii": "ERROR_EMISION",
"motivo": "ENVIO_INTENTOS_AGOTADOS",
"rutEmisor": "76123456-7",
"rutReceptor": "77999888-1",
"razonSocialReceptor": "Cliente SpA",
"montoTotal": 119000
}
}
estadoSiimotivoQué pasó
ERROR_EMISIONENVIO_INTENTOS_AGOTADOSLa plataforma intentó subir el documento al SII y no obtuvo confirmación tras agotar los reintentos. El envío pudo haber llegado
FALLIDO_RECONCILIACIONENVIO_INTENTOS_AGOTADOSUna boleta cuyo envío no se confirmó: la plataforma consultó al SII y no la encontró, o agotó los intentos de consultarla sin respuesta concluyente
ERROR_EMISIONENVIO_PLAZO_VENCIDOEl documento estuvo más de 24 horas sin subirse al SII. El SII no lo conoce por esta vía
TIMEOUT_SIISEGUIMIENTO_AGOTADOEl documento se envió y el SII no dio un estado final dentro del plazo de seguimiento

No lleva el mensaje de error ni ningún detalle interno de la plataforma: sólo el motivo de catálogo.

No es definitivo cuando el motivo es SEGUIMIENTO_AGOTADO: si después la plataforma obtiene un veredicto del SII, vas a recibir dte.emision_terminada — ésa es la última palabra.

Qué corresponde hacer en cada caso, y por qué no conviene reemitir con la misma idempotencyKey, está en Modos de emisión.


Una guía de despacho (52) quedó anulada: pasó a ANULADO y entra al Libro de Guías con código 1 o 2 (cómo se anula). Llega una sola vez por guía: repetir la anulación con el mismo cuerpo no lo vuelve a enviar. También llega cuando un reemplazo queda vigente y la guía anterior se anula sola; en ese caso guiaReemplazoId trae la guía nueva.

No es opt-in estricto: con «Eventos habilitados» vacío lo recibes, como la mayoría de los eventos.

{
"event": "dte.guia_anulada",
"tenantId": "8",
"timestamp": "2026-10-17T15:04:05.000Z",
"data": {
"dteId": "dte_5fe4ff45-...",
"tipoDte": 52,
"folio": 1234,
"emisorRut": "71669300-7",
"codigo": 2,
"motivo": "CAMBIO_PATENTE",
"montoModificado": null,
"guiaReemplazoId": "dte_9a8b7c6d-...",
"anuladaEn": "2026-10-17T15:04:05.000Z"
}
}
CampoQué es
dteId, folioLa guía anulada. tipoDte es siempre 52 y folio un número
emisorRutRUT del emisor de la guía
codigo1 si el folio se anuló sin haber llegado al SII, 2 si la guía ya había llegado
motivoUno de los motivos del catálogo de la anulación (ERROR_DIGITACION, CAMBIO_PATENTE, OTRO…)
montoModificadoSiempre null en este evento
guiaReemplazoIdId de la guía que la reemplaza, o null si se anuló sin reemplazo
anuladaEnFecha y hora en que quedó registrada la anulación

Si tu endpoint recibe el mismo aviso dos veces por un reintento, deduplica por data.dteId. El historial de entregas muestra la guía como «Guía de Despacho 1234» (tipo y folio).


Una guía de despacho (52) vigente quedó modificada por recepción parcial: se anuló con recepcionParcial: true y entra al Libro de Guías con código 3. La guía sigue vigente: su estado no cambia. Llega una sola vez por guía.

{
"event": "dte.guia_modificada",
"tenantId": "8",
"timestamp": "2026-10-17T15:04:05.000Z",
"data": {
"dteId": "dte_5fe4ff45-...",
"tipoDte": 52,
"folio": 1234,
"emisorRut": "71669300-7",
"codigo": 3,
"motivo": "RECEPCION_PARCIAL",
"montoModificado": 80000,
"guiaReemplazoId": null,
"anuladaEn": "2026-10-17T15:04:05.000Z"
}
}

Mismos campos que dte.guia_anulada. codigo es siempre 3, montoModificado trae el monto en pesos que declaraste (o null si no lo informaste) y guiaReemplazoId es null.


El SII aceptó la anulación de la boleta. Mismo payload para ambos eventos; cambia cuál de bheId / bhetId viene poblado.

{
"event": "bhe.anulada",
"tenantId": "8",
"anulacionId": "cm5anul123",
"tipoBoleta": "BHE",
"folioSii": "12345678",
"bheId": "cm5abc123",
"causaSii": "3",
"codigoResultadoSii": "0",
"mensajeSii": "Anulación realizada con éxito",
"fechaAnulacionSii": "2026-08-13T12:30:00.000Z",
"correlationId": "mi-referencia-123"
}

bhe.anulacion_fallida / bhet.anulacion_fallida

Sección titulada «bhe.anulacion_fallida / bhet.anulacion_fallida»

El SII rechazó la anulación. Mismos campos que el evento de éxito; mensajeSii y codigoResultadoSii traen el motivo del rechazo y fechaAnulacionSii no viene.

{
"event": "bhe.anulacion_fallida",
"tenantId": "8",
"anulacionId": "cm5anul123",
"tipoBoleta": "BHE",
"folioSii": "12345678",
"bheId": "cm5abc123",
"causaSii": "3",
"codigoResultadoSii": "-1",
"mensajeSii": "La boleta no puede anularse: período ya declarado",
"correlationId": "mi-referencia-123"
}

Una boleta que ya estaba anulada no genera evento: desde tu lado no cambió nada.


Un proceso batch terminó. Recuerda que es opt-in estricto: hay que listarlo explícitamente en «Eventos habilitados».

{
"event": "batch.completado",
"tenantId": "8",
"timestamp": "2026-08-13T12:30:00.000Z",
"data": {
"procesoBatchId": "cm5batch123",
"tipo": "BHE_EMISION",
"estado": "COMPLETADO",
"nombre": "Carga honorarios agosto",
"resumen": {
"total": 120,
"exitosos": 118,
"fallidos": 2,
"porcentaje": 98
},
"emisor": null,
"fechas": {
"creado": "2026-08-13T12:00:00.000Z",
"iniciado": "2026-08-13T12:00:05.000Z",
"finalizado": "2026-08-13T12:29:41.000Z"
},
"urlResultados": "/acme/procesos-batch/cm5batch123/resultados"
}
}

emisor es siempre null: cada item del proceso puede tener un emisor distinto, así que no hay uno global. estado es COMPLETADO tanto si todos los items salieron bien como si algunos fallaron — mira resumen.fallidos para distinguirlo.


Eventos que aparecen en la configuración pero todavía no se despachan

Sección titulada «Eventos que aparecen en la configuración pero todavía no se despachan»

La pantalla de configuración permite seleccionar algunos eventos que hoy no llegan a ningún endpoint. Están declarados en el catálogo, pero no hay nada que los emita:

EventoAlternativa
dte.boleta.aceptada, dte.boleta.rechazada, dte.boleta.reparoSe retiraron: describían para boletas el mismo hito que dte.emision_terminada cubre para todo tipo de documento. Migra a ese evento y ramifica por resultado
onexo.postulacion-completada—
onexo.postulacion-aprobada—
onexo.postulacion-rechazada—
onexo.postulacion.mensaje-enviado—
onexo.aclaracion-solicitadaDeprecado desde abril de 2026
onexo.curso-aprobado—
validafirma.documento-completado—

Cada webhook incluye una firma HMAC-SHA256 en el header X-Webhook-Signature que debes validar para asegurar que el webhook es auténtico. Todo webhook va firmado, a cualquier destino y con «Enviar a esta URL» encendido o apagado. Si la firma no se puede calcular, el aviso no sale: queda en el historial de entregas como fallido «sin secreto» y se reintenta.

El header no es el digest a secas: usa el esquema con timestamp, igual que Stripe.

X-Webhook-Signature: t=<unix_timestamp>,v1=<hmac_hex>

Mientras dura una rotación del secreto el header lleva dos v1, una por secreto, con el mismo t:

X-Webhook-Signature: t=<unix_timestamp>,v1=<hmac_con_el_nuevo>,v1=<hmac_con_el_anterior>
ParteContenido
tTimestamp Unix en segundos, el mismo que entra en el cálculo de la firma
v1HMAC-SHA256 en hexadecimal minúscula (64 caracteres). Puede venir una o dos veces

Acepta el aviso si cualquiera de las v1 coincide con la que calculas con tu secreto. Así tu receptor sigue validando durante la rotación, tenga cargado el secreto nuevo o el anterior.

No se firma el payload solo, sino la concatenación <timestamp>.<body>:

firma = HMAC_SHA256(secret, `${t}.${rawBody}`) → hex
const crypto = require('crypto');
function validarFirma(rawBody, header, secret) {
if (!header) return false;
// El header puede traer dos `v1` durante una rotación del secreto: se juntan todas.
let t;
const firmas = [];
for (const parte of header.split(',')) {
const i = parte.indexOf('=');
const clave = parte.slice(0, i);
const valor = parte.slice(i + 1);
if (clave === 't') t = valor;
if (clave === 'v1') firmas.push(valor);
}
if (!t || firmas.length === 0) return false;
// Rechazar firmas viejas (protección contra replay). Tolerancia sugerida: 5 minutos.
const edad = Math.abs(Math.floor(Date.now() / 1000) - Number(t));
if (edad > 300) return false;
const esperada = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
// Vale si CUALQUIERA de las firmas coincide.
// timingSafeEqual lanza si los buffers tienen distinto largo: comparar el largo primero.
const calculada = Buffer.from(esperada, 'hex');
return firmas.some((v1) => {
const recibida = Buffer.from(v1, 'hex');
return (
recibida.length === calculada.length &&
crypto.timingSafeEqual(recibida, calculada)
);
});
}
// Uso en endpoint receptor
app.post(
'/webhooks/redcumbre',
express.raw({ type: 'application/json' }),
(req, res) => {
const rawBody = req.body.toString('utf8');
const secret = process.env.REDCUMBRE_WEBHOOK_SECRET;
if (!validarFirma(rawBody, req.headers['x-webhook-signature'], secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, ...data } = JSON.parse(rawBody);
console.log(`Received ${event}:`, data);
res.status(200).json({ success: true });
}
);
import hmac, hashlib, time
def validar_firma(raw_body: bytes, header: str, secret: str) -> bool:
if not header:
return False
# El header puede traer dos `v1` durante una rotación del secreto: se juntan todas.
t, firmas = None, []
for parte in header.split(","):
clave, _, valor = parte.partition("=")
if clave == "t":
t = valor
elif clave == "v1":
firmas.append(valor)
if not t or not firmas:
return False
if abs(int(time.time()) - int(t)) > 300:
return False
esperada = hmac.new(
secret.encode(),
f"{t}.".encode() + raw_body,
hashlib.sha256,
).hexdigest()
# Vale si CUALQUIERA de las firmas coincide.
return any(hmac.compare_digest(v1, esperada) for v1 in firmas)

Desde General → Webhooks, pestaña «Configuración», puedes regenerar el secreto. Al regenerar eliges qué pasa con el anterior:

OpciónQué pasa
Vencer el anterior ahoraDesde ese momento todo webhook —también los reintentos en curso— va firmado sólo con el secreto nuevo. Actualiza tu receptor antes, o vas a rechazar como inválidos los avisos de esa ventana
Mantener el anterior hasta 24 horasDurante ese plazo cada webhook lleva dos v1, una por secreto, y el panel muestra hasta cuándo vale el anterior. Tienes ese tiempo para cargar el nuevo en tu receptor sin rechazar nada

Nunca hay más de dos secretos vigentes. Regenerar con «vencer ahora» durante una rotación deja sólo el nuevo.

Es una acción del panel, no de la API pública: no hay endpoint con API key para regenerarlo, y no existe acción que borre el secreto.


Aplica a la emisión asíncrona de BHE, BHET y DTE —en DTE, sólo a la que queda encolada con intentoId; la firma local no pasa por acá—. Los tres carriles comparten la misma curva: 10 intentos, con la espera duplicándose desde 5 minutos y un techo de 6 horas por hueco.

Intento que fallóEspera hasta el siguienteAcumulado
15 min5 min
210 min15 min
320 min35 min
440 min1 h 15 min
51 h 20 min2 h 35 min
62 h 40 min5 h 15 min
75 h 20 min10 h 35 min
86 h (techo)16 h 35 min
96 h (techo)22 h 35 min

A cada espera se le aplica un jitter que sólo puede restar hasta un 20 %, para que dos emisiones distintas no golpeen el SII al mismo tiempo cuando vuelve de una caída. Por eso la ventana real va de 18 h a 22,6 h, no es un número fijo.

El jitter es determinista, no aleatorio: el proximoIntentoEn que viaja en *_emision_reintentando es el instante exacto en que el reintento va a ocurrir, y puedes agendar contra él.

Durante todo ese tiempo recibes *_emision_reintentando (informativo) y no debes reemitir. Sólo al final, si se agotaron los 10 intentos, llega *_emision_fallida — una sola vez. Un error del SII que no se puede recuperar reintentando (contribuyente no habilitado, RUT inválido, certificado vencido, CAF vencido) corta la curva de inmediato y salta directo a *_emision_fallida en el primer intento.

Cuando tu endpoint no responde 2xx, reintentamos el POST 12 veces, con la espera duplicándose desde 1 segundo y un techo de 1 minuto por hueco:

IntentoEspera hasta el siguienteAcumulado
1+1 s1 s
2+2 s3 s
3+4 s7 s
4+8 s15 s
5+16 s31 s
6+32 s1 min 3 s
7+1 min (techo)2 min 3 s
8+1 min3 min 3 s
9+1 min4 min 3 s
10+1 min5 min 3 s
11+1 min6 min 3 s
12 (final)——

Total: ~6 minutos desde el primer intento hasta el último. La ventana es exacta, sin variación aleatoria: puedes dimensionar tu ventana de despliegue contra ella.

El techo de 1 minuto es deliberado. Los primeros intentos son rápidos porque un 502 puntual se resuelve en segundos; a partir del séptimo la espera se estabiliza en un minuto, así que un endpoint que vuelve a los tres minutos recibe el evento pendiente dentro del minuto siguiente, en vez de esperar a que una curva exponencial cierre su último hueco.


Una URL que redirige (301, 302, 307…) no recibe el aviso: Redcumbre no sigue redirecciones al entregar un webhook, y cuenta la respuesta como intento fallido. Configura la URL final.

Código HTTPResultado
200-299Webhook recibido correctamente
3xxEntrega fallida - No se sigue la redirección; se reintentará y queda en el historial con su código
4xxError cliente - Se reintentará
5xxError servidor - Se reintentará
TimeoutSin respuesta en 30s - Se reintentará

En General → Webhooks, pestaña «Historial de entregas» (y en la pestaña Webhooks del panel de PINRUT), ves cada aviso que Redcumbre intentó enviar por tu empresa y cada evento que no salió:

  • Entregada: tu endpoint respondió 2xx.
  • Fallida: agotó sus intentos sin 2xx (también un 3xx, un timeout o «sin secreto»).
  • No enviada: no había a dónde enviarlo, con el motivo (sin URL por defecto, envío apagado, evento fuera de la lista).

Cada entrega muestra el evento, la referencia de su operación, el destino, el código y, al abrirla, cada intento con su hora, su código y el comienzo de la respuesta de tu endpoint. Puedes filtrar por resultado, evento y fechas.

El historial guarda 30 días y no guarda el contenido de los avisos: sirve para saber qué pasó, no para reenviar. En modo de prueba ves sólo las entregas de prueba, y en modo real sólo las reales.


  1. Responde rápido (< 5 segundos)

    • Timeout máximo: 30 segundos
    • Procesa en background si necesitas más tiempo
  2. Retorna HTTP 200-299 siempre que recibas el webhook

    { "success": true, "receivedAt": "2025-12-10T15:30:00Z" }
  3. Implementa idempotencia

    • Los reintentos pueden enviar el mismo webhook múltiples veces
    • Usa timestamp o un campo único para detectar duplicados
  4. Valida la firma HMAC

    • Nunca proceses webhooks sin validar la firma
    • Usa comparación timing-safe
  5. Logea todos los webhooks recibidos

    • Ayuda al debugging
    • Permite detectar problemas de entrega

webhook.site es una herramienta gratuita para testear webhooks:

  1. Ve a https://webhook.site
  2. Copia tu URL única (ej: https://webhook.site/abc-123)
  3. Guárdala como URL por defecto en General → Webhooks
  4. Realiza una operación que genere webhook (ej: emitir BHE)
  5. Verifica en webhook.site que recibiste el POST

Para testear la lógica de reintentos, puedes usar URLs que retornan errores:

Ventana de terminal
# Retorna siempre HTTP 500
https://httpstat.us/500
# Retorna HTTP 503
https://httpstat.us/503
# Delay de 35 segundos (causa timeout)
https://httpbin.org/delay/35

En modo sandbox, los webhooks funcionan normalmente pero los datos son de prueba:

  • API Keys con isSandbox: true envían webhooks con datos simulados
  • No hay llamadas reales al SII
  • Útil para validar tu integración antes de producción

Los eventos de prueba van a la URL por defecto de prueba de tu empresa —o a la URL que declaró la operación—, con sandbox: true en el payload, firmados con el secreto de prueba. Sus entregas se ven en el historial con el panel en modo de prueba. El catálogo de RUT de prueba está en Modo de prueba.


Los webhooks son notificaciones salientes: tu sistema las recibe, no las llama. Por eso no tienen una sección propia en Swagger. El detalle del payload de cada evento está en el tag del dominio que lo emite —por ejemplo, tokenizacion.completada se documenta dentro de Tokenización SII— y los ejemplos completos están más arriba en esta misma guía.