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), configurás una URL y nosotros te avisamos cuando algo sucede.
Flujo de Webhooks
Sección titulada «Flujo de Webhooks»Tu Sistema Redcumbre Evento │ │ │ │── Configura webhook URL ─────▶│ │ │ (POST /config/webhooks) │ │ │◀── Secret HMAC ──────────────│ │ │ │ │ │ │◀─────── 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
- Firma HMAC-SHA256 para validar autenticidad
- Headers personalizados para autenticación
Configuración de Webhooks
Sección titulada «Configuración de Webhooks»La configuración de webhooks se realiza desde el panel de administración de Redcumbre:
Administración → Configuración → Webhooks
Opciones de Configuración
Sección titulada «Opciones de Configuración»| Campo | Descripción |
|---|---|
| URL del Webhook | URL HTTPS donde recibirás las notificaciones |
| Headers Personalizados | Headers HTTP adicionales para autenticación (ej: Authorization: Bearer token) |
| Eventos Habilitados | Selecciona qué eventos quieres recibir. Dejarlo vacío significa “todos”, no “ninguno” — y eso incluye los eventos que se agreguen en el futuro. El filtro aplica cualquiera sea el destino, también al callback_url de PIN-RUT |
| Activo | Habilitar/deshabilitar el envío a la URL del Webhook. No alcanza a los destinos que una operación declara por sí misma, como el callback_url de una transacción PIN-RUT: esos se siguen enviando, con los headers personalizados |
Secret HMAC
Sección titulada «Secret HMAC»Al crear la configuración, el sistema genera automáticamente un secret de 64 caracteres hexadecimales. Este secret se usa para firmar los webhooks con HMAC-SHA256.
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 |
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_reintentando | Un intento de emisión asíncrona de DTE falló y va a reintentarse |
dte.emision_fallida | Emisión asíncrona de DTE falló definitivamente. Se envía una sola vez |
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. Ya puedes entregárselo a tu receptor |
| El SII lo resolvió | dte.emision_terminada | El SII lo aceptó, lo aceptó con reparos o lo rechazó |
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.
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 PIN-RUT
Sección titulada «Eventos PIN-RUT»| 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. Vacío mientras espera la certificación |
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 |
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) |
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.
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.
{ "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.
{ "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 |
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 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.
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 |
dte.emision_terminada | Consulta estadoPdfSii del DTE |
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. La firma solo se envía si tu configuración de webhooks
está activa y tiene un secret asignado (Administración → Configuración → Webhooks).
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>| 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) |
El header completo mide 80 caracteres mientras el timestamp tenga 10 dígitos.
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;
const partes = Object.fromEntries( header.split(',').map((p) => { const i = p.indexOf('='); return [p.slice(0, i), p.slice(i + 1)]; }) ); const { t, v1 } = partes; if (!t || !v1) 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');
// timingSafeEqual lanza si los buffers tienen distinto largo: comparar el largo primero. const recibida = Buffer.from(v1, 'hex'); const calculada = Buffer.from(esperada, '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
partes = dict(p.split("=", 1) for p in header.split(",")) t, v1 = partes.get("t"), partes.get("v1") if not t or not v1: 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()
return hmac.compare_digest(v1, esperada)Regenerar el secret
Sección titulada «Regenerar el secret»Desde Administración → Configuración → Webhooks puedes regenerar el secret. La acción invalida el anterior de inmediato: todo webhook despachado después viaja firmado con el secret nuevo, sin período de gracia ni doble firma. Actualiza tu receptor antes de regenerar, o vas a rechazar como inválidos los eventos de esa ventana.
Es una acción del panel, no de la API pública: no hay endpoint con API key para regenerarlo.
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. 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»| Código HTTP | Resultado |
|---|---|
| 200-299 | Webhook recibido correctamente |
| 4xx | Error cliente - Se reintentará |
| 5xx | Error servidor - Se reintentará |
| Timeout | Sin respuesta en 30s - Se reintentará |
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) - Configura esa URL en tu tenant
- 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
RUTs de prueba:
| RUT | Resultado |
|---|---|
78012039-8 | Empresa completa (FIRERAISE SPA) |
77425402-1 | Empresa con múltiples direcciones |
99999999-9 | Simula error |
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.