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.
Flujo de Webhooks
Sección titulada «Flujo de Webhooks»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
Configuración de Webhooks
Sección titulada «Configuración de Webhooks»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:
| Bloque | Qué guarda | A qué destinos aplica |
|---|---|---|
| Firma y configuración por defecto | El secreto de firma y los headers personalizados | A todos los webhooks de tu empresa, vayan a la URL por defecto o a la URL que declaró una operación |
| URL por defecto | La URL, el interruptor «Enviar a esta URL» y la lista de eventos habilitados | Só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.
A dónde va cada aviso
Sección titulada «A dónde va cada aviso»La regla es una sola, y es la misma para todos los productos:
- 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_urlde una transacción de PINRUT, lawebhookUrlde una sesión de tokenización y lawebhookUrlde una verificación OTP. - 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.
- 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».
Secreto de firma
Sección titulada «Secreto de firma»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).
Headers personalizados
Sección titulada «Headers personalizados»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.
URL por defecto
Sección titulada «URL por defecto»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.
| Campo | Descripción |
|---|---|
| URL | URL HTTPS donde recibirás los avisos que no traen una URL propia |
| Enviar a esta URL | Enciende o apaga el envío a la URL por defecto. No alcanza a la URL que declaró una operación |
| Eventos habilitados | Qué 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.
Catálogo de Eventos
Sección titulada «Catálogo de Eventos»Eventos DTE (Documentos Tributarios)
Sección titulada «Eventos DTE (Documentos Tributarios)»| Evento | Descripción |
|---|---|
tokenizacion.completada | Emisor 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.actualizado | Un 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.creado | Alta directa de un emisor DTE o tributario, sin pasar por una sesión de tokenización. Opt-in estricto |
emisor.certificado | Un emisor DTE quedó productivo ante el SII. Uno por carril |
emisor.revocado | Emisor revocado (DTE o Tributario) |
bhe.emitida | Boleta de Honorarios emitida al SII |
bhe.emision_terminada | PDF SII disponible (modos SINC_PARCIAL/ASINCRONO) |
bhe.pdf_sii_fallido | PDF SII falló después de 7 días de reintentos |
bhe.emision_reintentando | Un intento de emisión asíncrona falló y va a reintentarse — informativo |
bhe.emision_fallida | Emisión asíncrona falló definitivamente. Se envía una sola vez |
bhe.anulada | Anulación de la BHE aceptada por el SII |
bhe.anulacion_fallida | El SII rechazó la anulación de la BHE |
bhet.emitida | Boleta de Terceros emitida al SII |
bhet.emision_terminada | PDF SII de la BHET disponible |
bhet.pdf_fallido | Generación del PDF SII de la BHET falló |
bhet.emision_reintentando | Un intento de emisión asíncrona de BHET falló y va a reintentarse |
bhet.emision_fallida | Emisión asíncrona de BHET falló definitivamente. Se envía una sola vez |
bhet.anulada | Anulación de la BHET aceptada por el SII |
bhet.anulacion_fallida | El SII rechazó la anulación de la BHET |
dte.emitido | El documento existe: folio, timbre y XML firmado. Opt-in estricto |
dte.emision_terminada | El SII resolvió el documento. Opt-in estricto |
dte.emision_sin_confirmar | La 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_reintentando | Un intento de emisión encolada de DTE (respuesta con intentoId) falló y va a reintentarse |
dte.emision_fallida | Emisión encolada de DTE (respuesta con intentoId) falló definitivamente. Se envía una sola vez |
dte.guia_anulada | Una guía de despacho (52) quedó anulada (códigos 1 y 2 del Libro de Guías) |
dte.guia_modificada | Una guía de despacho (52) vigente quedó modificada por recepción parcial (código 3) |
dte.credencial_portal_rechazada | El SII dejó de aceptar la credencial del portal del contribuyente. No detiene la emisión |
dte.credencial_portal_restablecida | La credencial del portal volvió a funcionar |
bhe.credencial_rechazada | El SII dejó de aceptar la credencial del emisor: la emisión BHE está detenida |
bhe.credencial_restablecida | La credencial volvió a funcionar y la emisión BHE se reanudó |
bhet.credencial_rechazada | Lo mismo, en el carril de boletas de terceros |
bhet.credencial_restablecida | La credencial volvió a funcionar y la emisión BHET se reanudó |
dte.acuse_reclamado | Documento 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_aceptado | Documento aceptado por el receptor. El receptor otorgó recibo y aceptó el contenido del documento ante el SII (ERM, ACD) |
otp.verification-created | Verificación OTP Creada. Verificación OTP creada y código enviado |
otp.verification-delivered | Verificación OTP Entregada. Código OTP entregado al destinatario |
otp.verification-approved | Verificación OTP Aprobada. Código OTP verificado correctamente |
otp.verification-failed | Verificación OTP Fallida. Verificación fallida por máximo de intentos o falla de entrega |
otp.verification-expired | Verificación OTP Expirada. Verificación expirada por TTL cumplido |
otp.verification-canceled | Verificación OTP Cancelada. Verificación cancelada manualmente o supersedida |
pin.transaction.authorized | Transacción PIN Autorizada. Verificación de identidad PIN completada exitosamente |
pin.transaction.failed | Transacción PIN Fallida. Verificación PIN fallida por PIN incorrecto o rate limit |
pin.transaction.expired | Transacció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.cancelled | Transacción PIN Cancelada. Transacción PIN cancelada por integrador |
whatsapp.canal.transferido | Nú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.entrante | Mensaje 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.eco | Mensaje 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.estado | Estado 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 |
Los dos hitos de un documento tributario
Sección titulada «Los dos hitos de un documento tributario»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.
| Hito | Evento | Qué afirma |
|---|---|---|
| El documento existe | dte.emitido | Tiene 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_terminada | El 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ón | Qué 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 externa | Si 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.
Eventos de procesos batch
Sección titulada «Eventos de procesos batch»| Evento | Descripción |
|---|---|
batch.completado | Proceso batch terminado (exitoso o con errores). Opt-in estricto, igual que los de boleta |
Eventos de Comunicación
Sección titulada «Eventos de Comunicación»| Evento | Descripción |
|---|---|
solicitud.creada | Se creó una solicitud en un formulario de Comunicación, entrara por la puerta que entrara |
solicitud.resuelta | El equipo cerró la solicitud. Trae el motivo |
solicitud.reabierta | Una 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.
Eventos PINRUT
Sección titulada «Eventos PINRUT»| Evento | Descripción |
|---|---|
kyc.identity.level_changed | Se 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 |
Payloads de Eventos
Sección titulada «Payloads de Eventos»tokenizacion.completada
Sección titulada «tokenizacion.completada»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"}El campo emisores, en sesiones DTE
Sección titulada «El campo emisores, en sesiones DTE»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": [] } ]}| Campo | Descripció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[].carril | PORTAL_MIPYME o FULL_DTE (facturación) · SOLO_BOLETA (boletas) |
emisores[].estado | OPERATIVO (puede emitir ya) o ESPERA_CERTIFICACION (nació inactivo, espera al SII) |
emisores[].motivoInactivo | PENDIENTE_CERTIFICACION, o null cuando el emisor está operativo |
emisores[].tiposHabilitados | Có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 porSOLO_BOLETA.FULL_DTEySOLO_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.
emisor.certificado
Sección titulada «emisor.certificado»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"}| Campo | Descripción |
|---|---|
tipoIntegracion | El carril que quedó certificado: FULL_DTE o SOLO_BOLETA. Es el discriminante: no lo deduzcas de tiposHabilitados |
tiposHabilitados | Códigos de tipo DTE que el emisor puede emitir a partir de ahora |
solicitudId | La solicitud de certificación que llegó al desenlace |
Los seis eventos de credencial del SII
Sección titulada «Los seis eventos de credencial del SII»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.
| Evento | Cuándo |
|---|---|
dte.credencial_portal_rechazada | El SII rechazó la credencial del portal del contribuyente |
dte.credencial_portal_restablecida | Volvió a aceptarla |
bhe.credencial_rechazada | El SII rechazó la credencial del emisor tributario, carril BHE |
bhe.credencial_restablecida | Volvió a aceptarla |
bhet.credencial_rechazada | Lo mismo, carril BHET |
bhet.credencial_restablecida | Volvió a aceptarla |
Rechazo
Sección titulada «Rechazo»{ "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"}| Campo | Descripción |
|---|---|
motivo | Lo que dijo el SII. null cuando rechazó sin dar una explicación reconocible: no inventamos una causa |
desde | Desde cuándo la credencial está rechazada |
queHacer | Texto listo para mostrarle a tu cliente |
titularNombre, titularRut | Quién tiene que corregirla en el SII. En DTE es el representante legal dueño del certificado; en bhe./bhet. coincide con el emisor |
Restablecimiento
Sección titulada «Restablecimiento»{ "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.
Cuántos eventos recibes
Sección titulada «Cuántos eventos recibes»- 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.
Cómo salir del episodio
Sección titulada «Cómo salir del episodio»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.
emisor.revocado
Sección titulada «emisor.revocado»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"}| Campo | Descripción |
|---|---|
emisorType | tributario o dte |
revokedBy | USER (usuario final) o ADMIN (administrador) |
motivoRevocacion | Motivo de revocación (solo cuando revokedBy: ADMIN) |
emisor.actualizado
Sección titulada «emisor.actualizado»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}| Campo | Descripción |
|---|---|
tipoEmisor | dte o tributario |
operation | UPDATE 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) |
camposModificados | Los 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 |
origen | PLATAFORMA 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 |
sandbox | true 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.
emisor.creado
Sección titulada «emisor.creado»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.
bhe.emitida
Sección titulada «bhe.emitida»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.
bhe.emision_terminada
Sección titulada «bhe.emision_terminada»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"}bhe.pdf_sii_fallido
Sección titulada «bhe.pdf_sii_fallido»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.
bhe.emision_reintentando
Sección titulada «bhe.emision_reintentando»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.
bhe.emision_fallida
Sección titulada «bhe.emision_fallida»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"}bhet.emitida
Sección titulada «bhet.emitida»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.
bhet.emision_terminada
Sección titulada «bhet.emision_terminada»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"}bhet.pdf_fallido
Sección titulada «bhet.pdf_fallido»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.
bhet.emision_reintentando
Sección titulada «bhet.emision_reintentando»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.
bhet.emision_fallida
Sección titulada «bhet.emision_fallida»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.
dte.emision_reintentando
Sección titulada «dte.emision_reintentando»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.
dte.emision_fallida
Sección titulada «dte.emision_fallida»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"}dte.emitido
Sección titulada «dte.emitido»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" }}dte.emision_terminada
Sección titulada «dte.emision_terminada»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.
resultado | Qué significa |
|---|---|
ACEPTADO | El documento tiene validez tributaria |
ACEPTADO_CON_REPAROS | Válido igual, pero el SII acusó observaciones. reparos trae el detalle |
RECHAZADO | El 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 rechazo | Qué significa |
|---|---|
codigo | La sigla cruda que devolvió el SII |
glosa | El texto del SII, o null si no dio uno reconocible |
alcance | ENVIO: 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.
dte.emision_sin_confirmar
Sección titulada «dte.emision_sin_confirmar»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 }}estadoSii | motivo | Qué pasó |
|---|---|---|
ERROR_EMISION | ENVIO_INTENTOS_AGOTADOS | La plataforma intentó subir el documento al SII y no obtuvo confirmación tras agotar los reintentos. El envío pudo haber llegado |
FALLIDO_RECONCILIACION | ENVIO_INTENTOS_AGOTADOS | Una 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_EMISION | ENVIO_PLAZO_VENCIDO | El documento estuvo más de 24 horas sin subirse al SII. El SII no lo conoce por esta vía |
TIMEOUT_SII | SEGUIMIENTO_AGOTADO | El 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.
dte.guia_anulada
Sección titulada «dte.guia_anulada»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" }}| Campo | Qué es |
|---|---|
dteId, folio | La guía anulada. tipoDte es siempre 52 y folio un número |
emisorRut | RUT del emisor de la guía |
codigo | 1 si el folio se anuló sin haber llegado al SII, 2 si la guía ya había llegado |
motivo | Uno de los motivos del catálogo de la anulación (ERROR_DIGITACION, CAMBIO_PATENTE, OTRO…) |
montoModificado | Siempre null en este evento |
guiaReemplazoId | Id de la guía que la reemplaza, o null si se anuló sin reemplazo |
anuladaEn | Fecha 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).
dte.guia_modificada
Sección titulada «dte.guia_modificada»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.
bhe.anulada / bhet.anulada
Sección titulada «bhe.anulada / bhet.anulada»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.
batch.completado
Sección titulada «batch.completado»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:
| Evento | Alternativa |
|---|---|
dte.boleta.aceptada, dte.boleta.rechazada, dte.boleta.reparo | Se 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-solicitada | Deprecado desde abril de 2026 |
onexo.curso-aprobado | — |
validafirma.documento-completado | — |
Seguridad: Validación HMAC
Sección titulada «Seguridad: Validación HMAC»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.
Formato del header
Sección titulada «Formato del header»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>| Parte | Contenido |
|---|---|
t | Timestamp Unix en segundos, el mismo que entra en el cálculo de la firma |
v1 | HMAC-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.
Qué se firma
Sección titulada «Qué se firma»No se firma el payload solo, sino la concatenación <timestamp>.<body>:
firma = HMAC_SHA256(secret, `${t}.${rawBody}`) → hexEjemplo en Node.js
Sección titulada «Ejemplo en Node.js»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 receptorapp.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 }); });Ejemplo en Python
Sección titulada «Ejemplo en Python»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)Regenerar el secreto
Sección titulada «Regenerar el secreto»Desde General → Webhooks, pestaña «Configuración», puedes regenerar el secreto. Al regenerar eliges qué pasa con el anterior:
| Opción | Qué pasa |
|---|---|
| Vencer el anterior ahora | Desde 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 horas | Durante 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.
Reintentos y Manejo de Errores
Sección titulada «Reintentos y Manejo de Errores»Reintentos de la emisión asíncrona
Sección titulada «Reintentos de la emisión asíncrona»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 siguiente | Acumulado |
|---|---|---|
| 1 | 5 min | 5 min |
| 2 | 10 min | 15 min |
| 3 | 20 min | 35 min |
| 4 | 40 min | 1 h 15 min |
| 5 | 1 h 20 min | 2 h 35 min |
| 6 | 2 h 40 min | 5 h 15 min |
| 7 | 5 h 20 min | 10 h 35 min |
| 8 | 6 h (techo) | 16 h 35 min |
| 9 | 6 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.
Reintentos de la entrega del webhook
Sección titulada «Reintentos de la entrega del webhook»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:
| Intento | Espera hasta el siguiente | Acumulado |
|---|---|---|
| 1 | +1 s | 1 s |
| 2 | +2 s | 3 s |
| 3 | +4 s | 7 s |
| 4 | +8 s | 15 s |
| 5 | +16 s | 31 s |
| 6 | +32 s | 1 min 3 s |
| 7 | +1 min (techo) | 2 min 3 s |
| 8 | +1 min | 3 min 3 s |
| 9 | +1 min | 4 min 3 s |
| 10 | +1 min | 5 min 3 s |
| 11 | +1 min | 6 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.
Respuestas Esperadas
Sección titulada «Respuestas Esperadas»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 HTTP | Resultado |
|---|---|
| 200-299 | Webhook recibido correctamente |
| 3xx | Entrega fallida - No se sigue la redirección; se reintentará y queda en el historial con su código |
| 4xx | Error cliente - Se reintentará |
| 5xx | Error servidor - Se reintentará |
| Timeout | Sin respuesta en 30s - Se reintentará |
Historial de entregas
Sección titulada «Historial de entregas»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.
Mejores Prácticas
Sección titulada «Mejores Prácticas»Para tu Sistema Receptor
Sección titulada «Para tu Sistema Receptor»-
Responde rápido (< 5 segundos)
- Timeout máximo: 30 segundos
- Procesa en background si necesitas más tiempo
-
Retorna HTTP 200-299 siempre que recibas el webhook
{ "success": true, "receivedAt": "2025-12-10T15:30:00Z" } -
Implementa idempotencia
- Los reintentos pueden enviar el mismo webhook múltiples veces
- Usa
timestampo un campo único para detectar duplicados
-
Valida la firma HMAC
- Nunca proceses webhooks sin validar la firma
- Usa comparación timing-safe
-
Logea todos los webhooks recibidos
- Ayuda al debugging
- Permite detectar problemas de entrega
Testing
Sección titulada «Testing»Usando webhook.site
Sección titulada «Usando webhook.site»webhook.site es una herramienta gratuita para testear webhooks:
- Ve a https://webhook.site
- Copia tu URL única (ej:
https://webhook.site/abc-123) - Guárdala como URL por defecto en General → Webhooks
- Realiza una operación que genere webhook (ej: emitir BHE)
- Verifica en webhook.site que recibiste el POST
Probando Reintentos
Sección titulada «Probando Reintentos»Para testear la lógica de reintentos, puedes usar URLs que retornan errores:
# Retorna siempre HTTP 500https://httpstat.us/500
# Retorna HTTP 503https://httpstat.us/503
# Delay de 35 segundos (causa timeout)https://httpbin.org/delay/35Testing (Sandbox)
Sección titulada «Testing (Sandbox)»En modo sandbox, los webhooks funcionan normalmente pero los datos son de prueba:
- API Keys con
isSandbox: trueenví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.
API Reference
Sección titulada «API Reference»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.