E-commerce para Empresas (B2B/B2C)

Webhooks de pago: cómo evitar pedidos duplicados y estados inconsistentes

Aprende a gestionar webhooks duplicados o tardíos con idempotencia, reintentos y conciliación para evitar pedidos duplicados y estados inconsistentes entre la pasarela y la tienda.

Tu webhook de pago falló: ¿tu tienda sabe qué ocurrió con la compra?

Una persona completa el pago en la pasarela, vuelve a tu tienda y ve una pantalla de confirmación. Minutos después, el pedido sigue marcado como “pendiente” en el panel administrativo. O peor: se despacha dos veces porque el mismo evento de pago llegó repetido. En ambos casos, el problema no está necesariamente en el checkout: está en cómo la tienda procesa, persiste y reconcilia los eventos posteriores al pago.

Integrar una pasarela no consiste solo en redirigir al cliente, recibir una respuesta y cambiar el estado de una orden. Un pago online es un flujo distribuido: la tienda, la pasarela, el banco o emisor y, en algunos casos, sistemas de inventario, facturación y logística deben llegar a una interpretación consistente de lo ocurrido.

Este artículo explica cómo diseñar una integración de pagos robusta frente a webhooks duplicados, entregas tardías, reintentos, fallos de red y estados inconsistentes, sin depender de una pasarela específica.

El checkout no confirma por sí solo el estado real de un pago

Es habitual que una integración tenga dos momentos visibles: la creación del intento de pago y el retorno del cliente a la tienda. Sin embargo, ninguno de los dos debería considerarse por sí solo la fuente definitiva de verdad sobre el resultado.

El navegador del cliente puede cerrar la pestaña, perder conectividad o no completar la redirección de retorno. Del otro lado, la pasarela puede haber procesado correctamente el pago y enviar la confirmación mediante un webhook varios minutos después. También puede ocurrir lo contrario: el cliente llega a una página de “pago exitoso”, pero la confirmación final aún está en proceso o requiere validación adicional.

Por eso conviene separar dos conceptos:

  • Experiencia del cliente: lo que se muestra tras finalizar el checkout, por ejemplo, “estamos confirmando tu pago”.
  • Estado operativo de la orden: el estado persistido y validado por el backend a partir de eventos confiables, consultas a la pasarela y reglas de negocio.

La URL de retorno puede servir para recuperar la sesión del usuario y mostrar el estado conocido de una orden, pero no debería ser el único mecanismo que confirma un pago. El backend necesita recibir y procesar eventos de forma independiente de la navegación del comprador.

Un flujo de pago robusto necesita estados explícitos

Cuando una orden pasa directamente de “creada” a “pagada”, el sistema pierde información importante: si el usuario abandonó el checkout, si la pasarela está procesando el cobro, si hubo un rechazo o si falta confirmar el resultado.

Un modelo de estados no tiene que ser complejo, pero debe representar las transiciones relevantes para el negocio. Por ejemplo:

  • CREATED: la orden fue creada y aún no existe una intención de pago confirmada.
  • PAYMENT_PENDING: la tienda inició un intento de pago y espera un resultado.
  • PAID: el pago fue confirmado mediante un evento validado o una consulta confiable a la pasarela.
  • PAYMENT_FAILED: el intento fue rechazado o falló de forma definitiva.
  • CANCELLED: la orden fue cancelada según una regla de negocio.
  • REFUNDED: el pago fue devuelto total o parcialmente, si el modelo de negocio necesita distinguirlo.

La definición exacta depende de la operación. Una tienda que reserva inventario durante pocos minutos puede requerir estados adicionales, como STOCK_RESERVED o RESERVATION_EXPIRED. Una empresa B2B con facturación posterior puede separar el estado de pago del estado de aprobación comercial.

Una orden no es lo mismo que un intento de pago

Una misma orden puede tener varios intentos: el primer pago puede ser rechazado y el comprador puede intentar nuevamente con otro medio. Por ello, suele ser más seguro modelar una entidad o registro de payment attempt asociado a la orden.

El intento de pago debería conservar identificadores externos, método de pago, importe, moneda, estado recibido, fecha de actualización y una referencia al evento que produjo el cambio. Esto permite responder preguntas operativas importantes: “¿cuál fue el último intento?”, “¿la orden fue pagada dos veces?” o “¿qué referencia debemos usar para investigar con la pasarela?”.

Qué ocurre cuando un webhook llega duplicado, tarde o fuera de orden

Los webhooks son notificaciones HTTP enviadas por una plataforma hacia tu backend. Son útiles porque desacoplan la confirmación del pago de la sesión del navegador, pero no deben asumirse como mensajes únicos, instantáneos ni ordenados.

En sistemas distribuidos, una entrega puede fallar después de que tu aplicación ya procesó el evento. Desde la perspectiva de la pasarela, no recibió una respuesta exitosa y puede reenviarlo. El resultado es un comportamiento conocido como entrega “al menos una vez”: el mismo evento puede llegar más de una vez.

También pueden existir retrasos. Un evento de aprobación puede llegar después de que la tienda haya marcado una orden como expirada por falta de pago. O un evento más reciente puede recibirse antes que otro anterior debido a reintentos, colas internas o problemas temporales de red.

Por ese motivo, un endpoint de webhook no debería contener una lógica del tipo “si llega un pago aprobado, crear factura y enviar pedido a despacho” sin controles adicionales. Esa decisión puede generar efectos duplicados.

La idempotencia evita repetir efectos de negocio

Una operación es idempotente cuando ejecutarla varias veces produce el mismo resultado final que ejecutarla una sola vez. En pagos, la idempotencia debe aplicarse a los efectos relevantes: marcar una orden como pagada, emitir una factura, enviar una notificación o solicitar preparación logística.

La forma más común de implementarla es persistir el identificador único del evento recibido, si la pasarela lo entrega, en una tabla o colección de eventos procesados. La base de datos debe impedir que ese identificador se registre dos veces.

BEGIN TRANSACTION;

INSERT INTO processed_payment_events (provider_event_id, received_at)
VALUES (:eventId, CURRENT_TIMESTAMP)
ON CONFLICT (provider_event_id) DO NOTHING;

-- Si el evento ya existía, finalizar sin repetir efectos.
-- Si fue insertado, validar la transición y actualizar el pago.

UPDATE payment_attempts
SET status = :newStatus,
    provider_reference = :paymentReference,
    updated_at = CURRENT_TIMESTAMP
WHERE provider_payment_id = :providerPaymentId;

COMMIT;

El detalle de la sintaxis depende del motor de base de datos, pero el principio es el mismo: registrar el evento y aplicar el cambio de estado de manera atómica. Si ambos pasos quedan separados y la aplicación falla entre ellos, será más difícil saber si el evento se procesó por completo.

Si el proveedor no expone un identificador único de evento, se puede construir una clave de deduplicación con datos estables, como el identificador externo del pago y el tipo de notificación. Aun así, debe analizarse cuidadosamente si esos datos distinguen eventos diferentes de forma suficiente.

Cómo procesar un webhook sin confiar ciegamente en su contenido

El endpoint que recibe webhooks es público por necesidad, pero no debería aceptar como válidas todas las solicitudes que llegan a su URL. Un atacante podría intentar enviar una notificación falsa para marcar una orden como pagada.

Las medidas concretas dependen de cada proveedor, pero una integración madura suele considerar los siguientes controles:

  • Validar autenticidad: verificar firma, secreto compartido, certificado u otro mecanismo ofrecido por la pasarela. La validación debe realizarse sobre el contenido original cuando el esquema de firma lo requiera.
  • Verificar correspondencia: comprobar que el identificador de pago recibido corresponde a una orden o intento de pago existente en la tienda.
  • Validar importe y moneda: el pago confirmado debe coincidir con lo esperado para ese intento. No basta con recibir un estado de aprobación.
  • Controlar transiciones: no todos los estados pueden pasar a cualquier otro. Una orden despachada, por ejemplo, no debería volver automáticamente a “pendiente” por un evento atrasado.
  • Registrar evidencia: guardar referencias técnicas y de negocio suficientes para investigar incidentes, evitando almacenar datos sensibles de tarjetas o información innecesaria.

En integraciones donde el webhook contiene información limitada, puede ser razonable consultar la API de la pasarela usando el identificador del pago antes de aplicar una transición sensible. Esto agrega una llamada externa y puede aumentar la latencia, pero reduce el riesgo de actuar sobre datos incompletos. La decisión depende del contrato de la pasarela, del nivel de confianza de su mecanismo de firma y de las reglas de riesgo del negocio.

Responder rápido no significa procesar superficialmente

Muchas pasarelas esperan una respuesta HTTP exitosa en poco tiempo. Si el procesamiento incluye facturación, actualización de inventario, envío de correos y comunicación con logística dentro de la misma solicitud, aumentan las probabilidades de timeout y reintentos.

Una estrategia frecuente consiste en validar lo esencial, persistir el evento de forma durable y responder. Después, un proceso interno puede ejecutar las acciones secundarias. Para evitar perder trabajo entre la actualización de la base de datos y la publicación de un mensaje, puede utilizarse el patrón transactional outbox: se registra una tarea o evento interno en la misma transacción que confirma el pago, y un proceso posterior lo publica o ejecuta.

No es obligatorio incorporar una arquitectura event-driven completa en una tienda pequeña. Un worker basado en la misma base de datos puede ser suficiente. Lo importante es que las acciones posteriores también sean recuperables e idempotentes.

La conciliación detecta pagos que el flujo en tiempo real no resolvió

Incluso con webhooks bien implementados, habrá casos que requieren conciliación: notificaciones que nunca llegaron, errores internos durante una ventana de mantenimiento, pagos aprobados mientras la tienda estaba caída o discrepancias entre el registro local y el proveedor.

La conciliación compara periódicamente los pagos registrados en la tienda con la información disponible en la pasarela. Puede realizarse mediante consultas a la API, reportes del proveedor o archivos de liquidación, según las capacidades de cada integración.

Un proceso de conciliación debería identificar, como mínimo:

  • intentos de pago pendientes durante más tiempo del esperado;
  • pagos aprobados en la pasarela que siguen pendientes o fallidos en la tienda;
  • órdenes marcadas como pagadas sin una referencia externa válida;
  • diferencias de importe, moneda o identificador de orden;
  • reembolsos o anulaciones que no se reflejaron internamente.

La conciliación no reemplaza a los webhooks. Los webhooks permiten reaccionar cerca del tiempo real; la conciliación aporta una red de seguridad y un mecanismo de auditoría. En operaciones con alto volumen o procesos contables estrictos, esta separación resulta especialmente importante.

Evita corregir discrepancias sin dejar trazabilidad

Si una conciliación detecta una diferencia, no siempre conviene actualizar el estado automáticamente. Algunas inconsistencias requieren revisión humana, especialmente si ya hubo despacho, devolución, fraude reportado o una transacción parcialmente reembolsada.

Es preferible registrar el hallazgo, la fuente de información, la decisión tomada y el usuario o proceso que realizó la corrección. Esa trazabilidad facilita soporte, auditoría y aprendizaje ante futuros incidentes.

Errores frecuentes al integrar pagos y cómo reducirlos

  • Confiar solo en la URL de retorno: el cliente puede no volver a la tienda. Usa el retorno para la experiencia de usuario y un mecanismo server-to-server para confirmar el resultado.
  • Actualizar una orden por cualquier webhook recibido: valida firma, referencia, importe, moneda y transición de estado antes de modificar datos de negocio.
  • No guardar el identificador de la transacción externa: sin esa referencia, soporte y conciliación se vuelven mucho más costosos.
  • Tratar un timeout como un rechazo: un timeout significa que no se conoce el resultado. Puede requerir reintento controlado o consulta posterior, no una cancelación inmediata.
  • Ejecutar efectos irreversibles dentro del webhook: despachar, facturar o descontar inventario sin idempotencia puede amplificar un evento duplicado.
  • No observar el flujo: sin logs estructurados, métricas y alertas, un webhook fallido puede descubrirse solo cuando un cliente reclama.
  • Mezclar el estado del pago con el de la entrega: una orden pagada no implica necesariamente que esté preparada, enviada o entregada.

La observabilidad debe permitir seguir una transacción desde la orden interna hasta la referencia externa de pago. Los logs deberían incluir identificadores de correlación, tipo de evento, resultado de validación y decisión tomada, sin exponer secretos, datos de tarjeta ni información personal innecesaria.

Pruebas que una integración de pagos debería superar antes de producción

Las pruebas no deberían limitarse al caso ideal de “pago aprobado una vez”. Un equipo de QA o desarrollo puede construir una matriz de escenarios que represente comportamientos habituales de sistemas distribuidos.

  • El mismo webhook llega dos o más veces.
  • El webhook llega después de que el cliente cerró el navegador.
  • El endpoint responde con error temporal y la pasarela reintenta.
  • La tienda procesa el evento, pero falla una tarea posterior como el envío de correo.
  • Un evento de rechazo llega después de un evento de aprobación, o viceversa.
  • La referencia externa no coincide con el intento de pago esperado.
  • El importe o la moneda no coinciden con la orden.
  • La base de datos no está disponible durante la recepción del webhook.
  • El proceso de conciliación encuentra un pago aprobado que no está reflejado localmente.

Estas pruebas pueden automatizarse con fixtures o simuladores internos que reproduzcan solicitudes firmadas de prueba, eventos duplicados y retrasos. La implementación exacta debe ajustarse a la documentación vigente de la pasarela elegida, especialmente en lo relativo a firmas, reintentos y ambientes de prueba.

Una integración confiable trata el pago como un proceso, no como una respuesta HTTP

Una tienda online confiable no asume que un pago se resuelve en el instante en que el usuario abandona el checkout. Modela intentos de pago, acepta que los eventos pueden repetirse o llegar tarde, valida cada notificación, hace idempotentes sus efectos y reconcilia periódicamente sus registros con la pasarela.

La decisión técnica más importante no es elegir una arquitectura excesivamente compleja, sino definir qué información necesita el negocio para conocer el estado real de una compra y qué mecanismos recuperan el sistema cuando la comunicación falla.

En Mentores Tech, el trabajo de arquitectura y desarrollo para tiendas online considera estos flujos de producción: consistencia de datos, integración backend, observabilidad, pruebas y operación posterior al lanzamiento.

Diseña la integración de pagos pensando también en duplicados, retrasos y fallos de comunicación.

Siguiente paso

E-commerce y presencia digital

Tiendas online con pagos y panel admin, o sitios web administrables.

Crear cuenta

Recibe recursos puntuales por correo (paso opcional).

Whatsapp Mentores Tech