Configura un endpoint de webhook de Stripe, verifica Stripe-Signature con el cuerpo sin procesar, prueba con stripe listen y stripe trigger, y completa los pedidos de Checkout una sola vez.
Un webhook de Stripe es un endpoint HTTPS en tu servidor al que Stripe envía por POST el JSON de un evento, como checkout.session.completed tras un pago. Tu handler debe verificar la cabecera Stripe-Signature contra el cuerpo sin procesar e intacto usando el secreto whsec_ del endpoint, devolver un 2xx rápido y tolerar que el mismo evento llegue más de una vez.
Lo esencial de los webhooks de Stripe (comprobado con la documentación de Stripe, octubre de 2026)
La mayoría de las integraciones rotas con Stripe fallan en uno de tres puntos: el framework procesó el cuerpo antes de la verificación, el handler hizo trabajo lento y agotó el tiempo, o un evento reintentado completó un pedido dos veces. Esta guía recorre un handler que evita los tres, con un comprobador abajo que verifica una cabecera Stripe-Signature en tu navegador para que veas exactamente lo que calcula la librería.
Casi siempre porque el cuerpo se modificó antes de la verificación, por ejemplo con express.json() o un body parser que vuelve a codificar el JSON. Monta express.raw({ type: "application/json" }) solo en la ruta del webhook, o lee request.text() en un route handler de Next.js. Un secreto incorrecto provoca el mismo mensaje.
checkout.session.completed para pagos con tarjeta, más checkout.session.async_payment_succeeded para métodos diferidos como los adeudos bancarios. Stripe también sugiere gestionar checkout.session.async_payment_failed para poder avisar al cliente. Comprueba el payment_status de la sesión antes de completar el pedido.
No. La propia documentación de Stripe dice que un cliente puede pagar y cerrar la pestaña antes de que cargue tu página de éxito. Usa la página de éxito para una confirmación rápida si quieres, pero el webhook también debe activar la entrega, y ambos caminos deben poder ejecutarse dos veces sin problema.
Stripe no garantiza el orden. invoice.paid puede llegar antes que customer.subscription.created. Registra los ids de evento para detectar duplicados y obtén el objeto relacionado desde la API cuando necesites su estado más reciente.
Haz clic en Resend en el evento dentro del Dashboard (funciona hasta 15 días después de su creación) o ejecuta stripe events resend <event_id> --webhook-endpoint=<endpoint_id> con la CLI (hasta 30 días). Los reintentos automáticos continúan de todos modos.
express.raw se monta solo en esta ruta, así el cuerpo llega a constructEvent con los bytes originales.
request.text() da el cuerpo sin procesar. Si constructEvent lanza un error, la firma o el secreto son incorrectos.
listen muestra un secreto whsec_ para esta sesión. Ponlo en tu entorno local y luego lanza eventos en una segunda terminal.
Tipos de evento comunes de Stripe y qué hacer
Pega una cabecera Stripe-Signature, el cuerpo sin procesar y tu secreto whsec_ para ver la misma comprobación que hacen las librerías. Se ejecuta en tu navegador con Web Crypto y no envía nada. Usa un secreto de modo de prueba, nunca uno de producción.
Cabecera Stripe-Signature
Cuerpo de la petición sin procesar
Secreto del endpoint (whsec_...)
Tolerancia en segundos
Firmar este cuerpo ahora
Verificar
Resultado
La cabecera no tiene timestamp t=.
La cabecera no tiene firma v1=.
v1 esperado: {v}
La firma coincide.
La firma no coincide: el cuerpo se modificó o el secreto es incorrecto.
El timestamp tiene {n} segundos, dentro de la tolerancia.
El timestamp tiene {n} segundos, fuera de la tolerancia: una librería lo rechazaría.
Empieza gratis · Sin tarjeta de crédito