Polling vs webhook
Hacienda demora segundos o minutos en resolver un comprobante. Existen dos formas de conocer el resultado: consultar repetidamente (polling) o recibir una notificación (webhook). La primera es simple, pero costosa y lenta; la segunda es la opción recomendada cuando es posible exponer un endpoint público.
Qué se reintenta y qué no
Conviene distinguir tres elementos que suelen confundirse en uno solo:
| Qué | Reintento |
|---|---|
| Enviar el comprobante a Hacienda | 5 intentos, backoff de 10 s a 5 min |
| Consultar a Hacienda el estado final | cada 10 min, sin límite de tiempo, hasta que responda |
| Entregar el webhook al integrador | 3 intentos in-process (0 s / 2 s / 8 s) + 2 por cron cada 10 min |
Las dos primeras corresponden a LX CloudPos frente a Hacienda; la tercera es la entrega hacia el integrador. Aun así, GET /api/v1/facturas/{clave} es siempre la fuente de verdad del estado: lo alimenta el barrido de la segunda fila, con independencia de si el webhook fue entregado.
Patrón polling (no recomendado)
Se consulta el detalle del comprobante en bucle hasta que cambie de estado. Resulta útil para pruebas locales o cuando no es posible exponer un endpoint público.
async function esperarHacienda(clave, maxIntentos = 30) {
for (let i = 0; i < maxIntentos; i++) {
const { data } = await axios.get(`/api/v1/facturas/${clave}`);
if (data.estadoHacienda === 'aceptado') return data;
if (data.estadoHacienda === 'rechazado') throw new Error('Rechazado');
await sleep(30000); // 30s
}
throw new Error('Timeout esperando Hacienda');
}Patrón webhook (recomendado)
El comprobante se emite y no requiere seguimiento activo. LX CloudPos notifica cuando Hacienda responde y el integrador únicamente lee el resultado de su propia base de datos.
// 1. Emisor: emite y olvida
await emitirFactura(data);
// 2. Receptor webhook: marca el resultado
app.post('/webhooks/lxs', verificarHmac, async (req, res) => {
const evento = parsearEvento(req.body);
await db.facturas.update({
where: { clave: evento.clave },
data: { estadoFinal: evento.ind_estado }
});
res.status(200).end();
});
// 3. Frontend: consulta el estado de la BD propia, sin tocar la API