Pagos e integraciones
Webhooks de Stripe: evita efectos duplicados, no solo eventos repetidos
Guardar un identificador de evento ayuda a reconocer una entrega repetida. Para proteger un pedido necesitas además decidir qué efecto de negocio puede ocurrir, en qué estado y cómo comprobarlo si falla un paso.
Stripe advierte de entregas duplicadas y sin orden garantizado. Su guía exige verificar la firma con el cuerpo original y responder con rapidez. Es documentación de referencia, no un lanzamiento de esta semana. (Stripe)
Dos claves para dos problemas
Una clave de recepción identifica qué mensaje has guardado. Una clave de efecto identifica qué operación quieres permitir sobre el negocio. Pueden coincidir en algún caso simple, pero no conviene diseñar el sistema suponiendo que son siempre equivalentes.
| Caso | Qué no resuelve por sí solo event.id | Control que debes definir |
|---|---|---|
| Mismo evento entregado otra vez | La recuperación si el primer procesamiento quedó a medias | Estado de la recepción y reintento seguro |
| Eventos distintos sobre un objeto | La unicidad del efecto comercial | Clave de efecto y transición permitida |
| Evento recibido tarde | La vigencia del estado que propone | Reglas de estado y evidencia autorizada |
| Petición saliente con resultado incierto | Lo ocurrido en el proveedor | Idempotencia saliente y reconciliación |
Aceptar significa haber conservado el trabajo
Nuestra propuesta es validar la petición y guardar una entrada durable antes de confirmar que se ha recibido. Una cola solo en memoria puede perder el trabajo al reiniciar. Si utilizas una base de datos y una cola separadas, define cómo recuperar una entrada guardada que todavía no llegó al trabajador.
pseudocódigo de diseño; no implementación lista para producción:
recibir(cuerpo_original, firma):
verificar firma con el secreto del endpoint
guardar en inbox con unicidad por cuenta + id de evento
si ya existe, mantener su estado de procesamiento
confirmar recepción solo después del guardado durable
recuperador:
localizar entradas pendientes que no estén en la cola
volver a ofrecerlas al trabajador de forma seguraLa firma acredita una propiedad de la entrega, no que tu aplicación haya interpretado bien el evento. Necesitas también elegir los tipos que atiendes, comprobar los datos requeridos y utilizar una lógica compatible con la versión de la integración. Un evento no relevante no debería producir una escritura comercial por caer en una rama genérica.
Procesar el efecto dentro de una frontera clara
Supongamos que el efecto consiste en confirmar un pedido tras comprobar un pago. La operación debería relacionar cuenta, objeto de pago y pedido, y comprobar la transición permitida. Si otro evento conduce a la misma confirmación, una restricción de unicidad y una transacción deben impedir crearla de nuevo.
Un ejemplo de clave de efecto podría combinar «confirmar pedido», el identificador del pedido y el objeto de pago correspondiente. Es una propuesta ilustrativa: las reglas concretas dependen de cobros parciales, devoluciones u otras operaciones. Una clave demasiado amplia puede suprimir trabajo legítimo; una demasiado estrecha puede duplicarlo.
trabajador(inbox_id):
cargar la evidencia necesaria para el evento
iniciar transacción
comprobar estado del pedido y transición permitida
registrar efecto único y cambio de datos de forma atómica
guardar tareas externas en outbox
marcar la entrada procesada
confirmar transacción
outbox:
entregar cada tarea con control propio de reintento y estadoLa transacción no se extiende mágicamente al exterior
Un correo o una petición a otro servicio puede completarse aunque tu proceso no reciba su respuesta. Por eso el outbox necesita sus propios estados y una vía para comprobar resultados ambiguos. No describimos el diseño como «exactly once»: los límites entre sistemas exigen recuperar y reconciliar.
La API de Stripe dispone de claves de idempotencia para solicitudes salientes compatibles. Ese mecanismo pertenece a las peticiones que envías; no sustituye la lógica de recepción de webhooks. (Stripe)
Después de un timeout, conserva la identidad de la operación y consulta la documentación de la API y versión utilizadas antes de repetir. Crear una clave nueva por cada reintento puede tratar la repetición como otra operación. Tampoco reutilices una clave para una intención comercial diferente.
Pruebas que deberían preceder a la conexión real
- Entregar dos veces el mismo evento y comprobar tanto el estado de recepción como el del pedido.
- Presentar eventos distintos que proponen el mismo efecto y verificar que la regla de negocio se conserva.
- Invertir un orden esperado y observar si el sistema retiene, consulta o reconcilia sin perder trazabilidad.
- Interrumpir un trabajador después de guardar datos y antes de una tarea externa.
- Simular un resultado incierto del proveedor y comprobar la recuperación autorizada.
Estas pruebas son una propuesta y no se han ejecutado en esta pieza. Deben utilizar datos y entornos de prueba, sin simular ventas o contactos reales. El resultado aceptable incluye errores visibles y trabajo recuperable, no solo la ausencia de excepciones.
Si encargas una integración de pagos, acuerda estas fronteras de recepción, efecto y reconciliación como parte de la aceptación. La entrega de un webhook y la finalización de un proceso comercial son estados distintos.
Fuentes
- Receive Stripe events in your webhook endpoint — Stripe
- Idempotent requests — Stripe