Facturiadocs
Webhooks

Entrega, reintentos y replay

El calendario de reintentos con jitter, el auto-apagado a los 5 días, y por qué el libro de eventos vive 12 meses.

El contrato de entrega

  • Responde 2xx en 10 segundos. Cualquier otra cosa — código distinto de 2xx, timeout, fallo de TLS — cuenta como falla. Haz tu trabajo de forma asíncrona: acusa primero.
  • Entrega al menos una vez y sin orden garantizado. Los eventos pueden llegar dos veces y desordenados. Deduplica por id (evt_…) e ignora cualquier evento cuyo data.object sea más viejo que el que ya tienes. Que document.accepted llegue antes que document.sent es normal y no puede romperte.

El calendario de reintentos

Inmediatamente, y después 30 s, 2 min, 10 min, 1 h, 3 h, 6 h, 12 h, 24 h — 9 intentos en unas 48 horas, con jitter.

El jitter existe para que una caída de tu lado no produzca una estampida sincronizada cuando vuelvas.

Trampa

A los 5 días de falla total, el endpoint se apaga solo

Después de 5 días corridos de falla total, el endpoint pasa a status: disabled, mandamos un correo a la cuenta y emitimos webhook_endpoint.disabled.

Un endpoint apagado no acumula reintentos: los eventos siguen en el libro, pero nadie te los está empujando. Reactivarlo no reenvía lo perdido automáticamente — para eso está el replay.

Inspeccionar y reintentar a mano

# Qué intentamos entregar, y cómo fue
curl -G https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W/deliveries \
  -H "Authorization: Bearer $FACTURIA_KEY" -d status=failed

# Reintentar una entrega puntual
curl -X POST https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W/deliveries/whd_01K2RG1P4M7X01/retry \
  -H "Authorization: Bearer $FACTURIA_KEY"

El libro de eventos

curl -G https://api.facturia.cl/v1/events \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -d "type=document.accepted" -d "created_at[gte]=2026-08-14T00:00:00Z"

curl https://api.facturia.cl/v1/events/evt_01K2RG1P4M7X \
  -H "Authorization: Bearer $FACTURIA_KEY"

12 meses de retención, a propósito

Los eventos se retienen 12 meses — deliberadamente más que el libro de webhooks habitual de 30 a 90 días, porque en este dominio la unidad de trabajo es un año tributario.

Una diferencia que aparece cerrando un período en marzo necesita que los eventos del julio anterior sigan estando, y reconstruir un ciclo completo de F29 desde el libro es un flujo soportado, no una emergencia.

Si tu consumidor estuvo caído una semana, haz replay en vez de reconciliar a mano.

Un consumidor que se porta bien

const procesados = new Set(); // en producción, una tabla con índice único

export async function manejar(evento) {
  if (procesados.has(evento.id)) return; // al menos una vez

  switch (evento.type) {
    case 'document.accepted':
      await marcarAceptado(evento.data.object);
      break;
    case 'document.rejected':
    case 'document.reparo':
      await avisarAOperaciones(evento.data.object);
      break;
    case 'received_document.deadline_approaching':
      await avisarPlazo(evento.data.object);
      break;
    default:
      // Tipos de evento nuevos aparecen sin previo aviso: ignóralos, no lances.
      break;
  }

  procesados.add(evento.id);
}

Esa rama default es la única regla de compatibilidad que te pedimos. Dentro de /v1 evolucionamos de forma aditiva, y un cliente que se cae con un tipo de evento desconocido se va a caer.

En esta página