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), configurás una URL y nosotros te avisamos cuando algo sucede.


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

La configuración de webhooks se realiza desde el panel de administración de Redcumbre:

Administración → Configuración → Webhooks

CampoDescripción
URL del WebhookURL HTTPS donde recibirás las notificaciones
Headers PersonalizadosHeaders HTTP adicionales para autenticación (ej: Authorization: Bearer token)
Eventos HabilitadosSelecciona 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
ActivoHabilitar/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

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.


EventoDescripción
tokenizacion.completadaEmisor autorizado exitosamente vía SII
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_reintentandoUn intento de emisión asíncrona de DTE falló y va a reintentarse
dte.emision_fallidaEmisión asíncrona de DTE falló definitivamente. Se envía una sola vez

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. Ya puedes entregárselo a tu receptor
El SII lo resolviódte.emision_terminadaEl 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ó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.

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
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. Vacío mientras espera la certificación

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

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 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 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.

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.

{
"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.

{
"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

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.


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
dte.emision_terminadaConsulta estadoPdfSii del DTE
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. La firma solo se envía si tu configuración de webhooks está activa y tiene un secret asignado (Administración → Configuración → Webhooks).

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>
ParteContenido
tTimestamp Unix en segundos, el mismo que entra en el cálculo de la firma
v1HMAC-SHA256 en hexadecimal minúscula (64 caracteres)

El header completo mide 80 caracteres mientras el timestamp tenga 10 dígitos.

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;
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 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
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)

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.


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 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.


Código HTTPResultado
200-299Webhook recibido correctamente
4xxError cliente - Se reintentará
5xxError servidor - Se reintentará
TimeoutSin respuesta en 30s - Se reintentará

  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. Configura esa URL en tu tenant
  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

RUTs de prueba:

RUTResultado
78012039-8Empresa completa (FIRERAISE SPA)
77425402-1Empresa con múltiples direcciones
99999999-9Simula error

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.