Un webhook de pago llega a las 02:14 durante una implementación. La aplicación responde 503. El proveedor vuelve a intentarlo tres veces. Por la mañana, el cliente tiene tres suscripciones activas y una sola factura. Nadie había implementado la idempotencia, solo un ingenuo "INSERT".
Los webhooks no son "devoluciones de llamadas HTTP geniales". Se trata de entregas realizadas al menos una vez con reintentos, pedidos no garantizados y, a veces, retrasos de minutos.
Modelo de entrega
Supongamos al menos una vez: duplicados normales. A veces pedidos no FIFO entre tipos de eventos. Su controlador debe ser idempotente y tolerar el reordenamiento lógico (por ejemplo, "cancelado" antes de "pagado", poco común pero posible).
Firma y marca de tiempo
Compruebe HMAC-SHA256 con secreto giratorio. Rechazar si la marca de tiempo es > 5 min (repetición). Comparaciones en tiempo constante para la firma.
Registre los errores de firma por separado de las 500 aplicaciones; no confunda un ataque con un error.
Idempotencia en la práctica
| Enfoque | Ventaja | Límite |
|---|---|---|
| event_id en base | Sencillo | Limpieza TTL |
| Encabezado de clave de idempotencia | Estándar API | Proveedor de soporte variable |
| Estado comercial (paid_at) | Embarcación robusta | Condiciones de raza |
Combine clave de proveedor + restricción única de SQL.
Respuestas HTTP y asincrónicas
Procesamiento < 2 s: 200 sincronismos. De lo contrario, 202 + cola (Redis, SQS, Rabbit). El trabajador toma event_id. Nunca hagas un trabajo pesado antes de absolver sin idempotencia.
Cola de mensajes fallidos por fallas comerciales después de N intentos.
Observabilidad
Métricas: latencia del controlador, tasa de 2xx/4xx/5xx, retraso de la cola, duplicados omitidos. Seguimiento correlacionado event_id → trabajo → factura. Runbook: “repetición manual sin doble efecto” documentada.
Lista de verificación de producción de endpoints
- [] Firma HMAC verificada en tiempo constante
- [] Marca de tiempo anti-repetición (<5 min)
- [] Tabla event_id restricción única
- [] Respuesta 2xx/202 <proveedor de tiempo de espera
- [] Cola asíncrona para procesamiento >2 s
- [ ] Letra muerta tras N fracasos empresariales
- [] Registros estructurados event_id + latencia
- [] Idempotencia probada (3 POST idénticos)
- [] Proveedor de IP del firewall documentado
- [] Manual de repetición de Runbook sin doble efecto
- [] Rotación secreta de webhook programada
- [ ] GDPR: retención limitada de la carga útil
Consulte esta lista antes de exponer la URL pública: un webhook "casi bueno" cuesta más que un retraso de lanzamiento de dos días.
Repetir escenarios e incidentes
Imagine una implementación que reinicia PHP-FPM durante treinta segundos: el proveedor de pagos envía cinco reintentos espaciados. Sin una tabla de idempotencia, acreditas el mismo pago cinco veces. El servicio de atención al cliente descubre el error antes de que su aplicación se registre porque el panel de contabilidad agrega duplicados más rápido que su monitoreo técnico.
Una repetición legítima difiere de una repetición maliciosa: la primera proviene del proveedor después del tiempo de espera, la segunda de un atacante que captura una URL de webhook sin firmar (si olvidó HMAC). La firma une el cuerpo al secreto; sin él, basta con reproducir un POST capturado.
Para las pruebas, utilice la herramienta de reproducción del proveedor (Stripe CLI, reentrega de GitHub) en la preparación con una base desechable. Compruebe que el tercer reintento idéntico no cambie nada en la base. Automatice esta prueba en CI si el punto final es crítico.
En el lado del alojamiento compartido, verifique max_execution_time PHP y el tiempo de espera del proxy nginx: una puerta de enlace 504 genera un proveedor de reintentos incluso si su controlador hubiera terminado dos segundos después. Alinear los tiempos de espera: nginx > PHP > tiempo de espera del webhook del proveedor, con margen.
Los proveedores difieren en el encabezado de la firma (Stripe-Signature, X-Hub-Signature-256, etc.): ajuste la verificación mediante el adaptador, no copie y pegue el documento una vez.
Versione el webhook secreto como contraseña: rotación con doble actividad secreta durante las 24 horas si el proveedor le brinda soporte.
Prueba de carga: simulación de 10 webhooks en ráfaga: profundidad de la cola y dimensiones del recuento de trabajadores antes del Black Friday.
Mantenga un runbook fechado, métricas de antes y después, revisión posterior al incidente: la disciplina acumulativa evita el pánico del viernes por la noche.
Mantenga un runbook fechado, métricas de antes y después, revisión posterior al incidente: la disciplina acumulativa evita el pánico del viernes por la noche.
Tiempos de espera compartidos
Alinee nginx > PHP > proveedor de tiempo de espera: 504 activa reintentos.
Decide y avanza sin puntos ciegos
- Verifique la firma HMAC en tiempo constante con una marca de tiempo antirrepetición de menos de cinco minutos.
- Deduplicar por event_id: restricción única en la base, idempotencia probada con tres POST idénticos.
- Repondre 2xx/202 rapidement — queue async si el tratamiento pasa dos segundos; letra muerta después de N fracasos empresariales.
- IP de proveedores de documentos: firewall, rotación de secretos de webhooks, reproducción manual del runbook sin doble efecto.
- Limitar la retención de carga útil: cumplimiento del RGPD en cuerpos almacenados.
Para elegir un alojamiento API fiable (latencia, SLA), utilice el comparador, el directorio y nuestras guías de observabilidad.
Preguntas frecuentes
¿Por qué firmar webhooks?
La firma HMAC demuestra que el cuerpo provino del proveedor y no fue alterado durante el transporte. Sin él, cualquiera puede PUBLICAR en su punto final.
¿Qué código HTTP para los reintentos?
2xx absuelto. 4xx no se puede volver a intentar si se produce un error permanente (firma no válida). 5xx o tiempo de espera activan reintentos, de ahí la idempotencia obligatoria.
¿Cómo deduplicar?
Almacene event_id (o hash estable) con restricción única; ignore los duplicados ya procesados incluso si el cuerpo difiere ligeramente.
¿Tiempo de espera en el lado del transmisor?
A menudo entre 5 y 30 segundos. Responda rápidamente (202 + trabajo asíncrono) si el procesamiento lleva mucho tiempo, pero luego garantice la idempotencia por parte del trabajador.
Antes de abrir el punto final en producción, simule tres POST idénticos; si la base de datos cambia tres veces, no está listo.
