Webhooks Confiables en Node.js: Idempotencia y DLQ con BullMQ
Tu webhook que "a veces falla" no falla a veces. Falla siempre que importa. Cuando el evento es una confirmación de pago de Stripe que el usuario ya vio en pantalla, cuando es un acuse del SAT que te notifica que el CFDI pasó a timbrado, cuando es un cambio de status de un envío que el cliente está esperando. En esos momentos tu receptor de webhooks está ocupado, la base de datos está lenta, o un deploy reciente rompió una ruta, y pierdes el evento. El emisor reintenta dos o tres veces con suerte, después te marca como "endpoint no confiable" y abandona.
Recibir webhooks bien no es tener un POST /webhook que responde 200. Es aceptar el evento en menos de 5 segundos sin importar qué, validar la firma, evitar procesar el mismo evento dos veces si llega duplicado, y procesarlo en background con reintentos exponenciales cuando falla. Todo eso se puede hacer con Node.js, Redis y BullMQ en menos de 300 líneas de TypeScript.
En este artículo aprenderás:
- Por qué los webhooks fallan en producción y qué contrato implícito tiene tu endpoint con cada emisor
- Patrón receive-fast-process-async con BullMQ para aceptar el evento en menos de 1 segundo
- Firma HMAC SHA-256 implementada bien, con comparación resistente a timing attacks
- Idempotencia por
idempotency-keycon Redis para no procesar el mismo evento dos veces - Retries con backoff exponencial y Dead Letter Queue para los eventos que fallan persistentemente
- Ejemplo real con SAT PAC y Stripe para aterrizar los patrones en casos conocidos
