← Volver a artículos

3 de junio de 2026

9 min de lectura

Integrar Stripe en producción: webhooks, idempotencia y flujos de pago que no fallan

Cómo construir un sistema de pagos con Stripe que soporte reintentos, fallas de red y eventos duplicados sin comprometer la consistencia.

Read in English

Los webhooks son el núcleo del sistema de pagos, no un detalle

Un error frecuente al integrar Stripe es tratar el webhook como un nice-to-have. En realidad, es el canal principal por el que el sistema recibe confirmaciones de pago, fallos, reembolsos y cambios de estado. Confiar solo en la respuesta del API al crear un PaymentIntent deja el sistema ciego ante lo que Stripe confirma de forma asíncrona.

Stripe puede reintentar un webhook hasta 64 veces en 72 horas si tu endpoint no responde con un 2xx. Eso significa que el handler tiene que ser idempotente: procesar el mismo evento dos veces no debe producir efectos duplicados. Si no está diseñado así desde el principio, la depuración en producción se vuelve costosa.

Verificar la firma del webhook antes de procesar cualquier evento

Stripe firma cada webhook con un secreto que generas en el dashboard. Verificar esa firma es el primer paso del handler: si la firma no es válida, el request viene de una fuente externa y debe rechazarse con un 400 antes de ejecutar cualquier lógica.

La verificación usa el timestamp incluido en la cabecera para evitar replay attacks. Si el evento tiene más de cinco minutos, Stripe lo considera expirado. El SDK oficial gestiona todo esto, pero hay que pasarle el body raw (sin parsear) y la cabecera completa, no el objeto JSON ya deserializado.

Handler de webhook con verificación de firma y respuesta inmediata.

import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const body = await req.text();
  const signature = req.headers.get('stripe-signature') ?? '';

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!,
    );
  } catch {
    return new Response('Invalid signature', { status: 400 });
  }

  // Responder 200 de inmediato; procesar en background
  processWebhookEvent(event).catch(console.error);

  return new Response('ok', { status: 200 });
}

Idempotencia: la garantía contra el procesamiento doble

Stripe puede enviar el mismo evento más de una vez. El campo `event.id` es el identificador único por evento. Antes de procesar, se debe verificar si ese ID ya fue procesado y guardarlo en base de datos al finalizar. Sin esta verificación, un reintento de Stripe puede acreditar un pago dos veces o enviar dos correos de confirmación.

El patrón estándar es una tabla `processed_webhook_events` con el `event.id` como clave primaria. Al recibir el evento, se intenta insertar ese ID. Si la inserción falla por duplicado, el evento ya fue procesado y se responde 200 sin hacer nada más. Si la inserción tiene éxito, se ejecuta la lógica y se confirma la transacción.

  • Guarda el event.id con un índice único antes de procesar.
  • Usa una transacción de base de datos para atomizar el guardado y el efecto.
  • Nunca asumas que el mismo tipo de evento llega una sola vez.

Flujos de pago que toleran fallas de red y reintentos

El flujo más seguro para un pago único es crear el PaymentIntent en el servidor con un `idempotency_key` generado desde el pedido, devolver el `client_secret` al frontend, confirmar el pago desde el cliente con Stripe.js y escuchar `payment_intent.succeeded` en el webhook para actualizar el estado del pedido.

Si el usuario cierra el navegador después de confirmar pero antes de que el frontend procese la respuesta, el webhook asegura que el pedido se actualice igual. Si el pago falla y el usuario reintenta, el mismo `idempotency_key` evita crear un PaymentIntent duplicado. Los dos mecanismos son complementarios, no alternativos.

  • Genera el idempotency_key desde el ID del pedido, no desde el session ID.
  • Actualiza el estado del pedido solo desde el webhook, nunca solo desde el frontend.
  • Testea el flujo de retry con las tarjetas de prueba de Stripe (4000002500003155).

Checklist antes de ir a producción con Stripe

Antes de activar el modo live, hay una lista corta de cosas que evitan el 90% de los problemas en producción: verificar que el webhook secret es el de producción (no el de test), confirmar que el endpoint responde en menos de 30 segundos (o delegar el procesamiento a un job), revisar que todos los eventos relevantes están suscritos en el dashboard y habilitar alertas de Stripe para failed deliveries.

También conviene implementar el portal de cliente de Stripe Billing si el proyecto tiene suscripciones. Eso reduce el soporte operativo porque los usuarios pueden actualizar tarjetas, cancelar y descargar facturas sin intervención del equipo.

Más artículos

Volver a artículos