Webhook de Stripe: cómo funciona en una app construida con IA

Cómo funciona un webhook de Stripe en una app hecha con Lovable, Bolt o Base44: dónde vive, firma, reintentos y protección contra pedidos duplicados.

Illustration of a Stripe webhook event passing a signature check, with retried duplicates deflected from the server
En esta página · 8 secciones
  1. ¿Qué es un webhook de Stripe y por qué mi app necesita uno?
  2. ¿Por qué la página de éxito no es prueba de pago?
  3. ¿Dónde debe vivir el endpoint del webhook en una app construida con IA?
  4. ¿Cómo configuro la URL del endpoint de webhook de Stripe?
  5. ¿Qué pasa si el webhook falla o llega dos veces?
  6. ¿Cómo evito los pedidos duplicados?
  7. ¿Puede un escáner de seguridad detectar bugs de webhooks?
  8. ¿Quieres una segunda opinión sobre tu flujo de pago?

Puntos clave

  • Quien debe decidir si un pedido está pagado es el webhook, no la página de éxito.
  • El endpoint debe ejecutarse en un servidor que tú controlas, nunca en código del navegador.
  • Verifica siempre la firma y trata cada evento como posiblemente tardío, perdido o duplicado.
  • Guarda el ID del evento de Stripe y el ID de la checkout session para que un pedido se entregue una sola vez.
  • Los escáneres de seguridad no entienden esta lógica; necesitas probar el propio flujo de pago.

Un webhook de Stripe es una solicitud HTTP que Stripe envía a tu servidor cuando le ocurre algo a un pago: se aprueba un cobro, se renueva una suscripción, se emite un reembolso. Es la única forma confiable de que tu backend conozca el resultado real de un pago, porque viene directamente de Stripe y no del navegador del cliente.

En apps construidas con herramientas de IA como Lovable, Bolt o Base44, el webhook, en las apps que he visto, a veces falta, está en el lugar equivocado o no se verifica. Soy un ingeniero sénior brasileño que ha trabajado en flujos de checkout, incluso en el equipo de carrito y checkout de Total Wine & More, y este es uno de los primeros lugares que reviso.

¿Qué es un webhook de Stripe y por qué mi app necesita uno?

Stripe procesa los pagos en sus propios servidores. Tu app solo se entera de lo que pasó si Stripe se lo dice. Un webhook es una URL en tu backend a la que Stripe llama con un evento en JSON que describe lo ocurrido, como checkout.session.completed o invoice.paid.

Tu handler lee el evento, comprueba que es auténtico y luego actualiza tu base de datos: marca el pedido como pagado, da acceso, envía el recibo. Sin eso, tu app está adivinando.

Los pagos además cambian después del primer momento. Las tarjetas reciben disputas, las suscripciones no se renuevan, se hacen reembolsos desde el dashboard de Stripe. Solo los webhooks mantienen tus registros al día con esos cambios posteriores.

¿Por qué la página de éxito no es prueba de pago?

Después del checkout, Stripe redirige al cliente a una URL de éxito en tu sitio. Algunas apps generadas con IA toman la llegada a esa página como prueba de pago y desbloquean el producto ahí. Eso es riesgoso por tres razones:

  • Cualquiera puede escribir la URL de éxito en el navegador sin pagar.
  • Un cliente puede cerrar la pestaña antes de la redirección, y entonces un pedido pagado nunca se registra.
  • Algunos métodos de pago se confirman más tarde, así que la redirección llega antes que el dinero.

La página de éxito es para la experiencia del usuario: mostrar un mensaje de agradecimiento. La decisión de que un pedido está pagado debe venir de un evento de webhook verificado, o de tu servidor consultando directamente a Stripe el estado de la sesión.

En mi experiencia, lo que genera el constructor puede verse bien en una demo. La prueba pasa porque el que pagó fuiste tú. El bug solo aparece cuando alguien se comporta de otra manera.

Ilustración tipo diagrama de una ruta de redirección del navegador y una ruta separada de confirmación en el servidor para un webhook de Stripe
La redirección del navegador y la confirmación del lado del servidor son dos caminos distintos; solo uno es confiable.

¿Dónde debe vivir el endpoint del webhook en una app construida con IA?

Debe vivir en un servidor, porque necesita tu clave secreta de Stripe y el secreto de firma del webhook. Ninguno de los dos puede llegar nunca al navegador. Dónde está ese servidor depende de tu stack:

  • Supabase: una Edge Function es el lugar habitual. Las apps construidas con Lovable suelen usar Supabase, así que es una opción común.
  • Next.js: un route handler en tu app, desplegado en tu propio hosting.
  • Un backend separado: por ejemplo NestJS o Go, si sacaste el código del constructor.

Hay dos detalles en los que mucha gente tropieza. Primero, el endpoint lo llama Stripe, no un usuario con sesión iniciada, así que no traerá tu token de autenticación normal. Lo proteges con la verificación de firma. Segundo, la verificación de firma necesita el cuerpo crudo de la solicitud. Si un framework parsea el JSON antes, la verificación falla, y el código generado a veces lo “arregla” quitando la verificación por completo. No aceptes ese arreglo.

Las reglas de datos también importan. El handler suele escribir con una clave privilegiada, así que debe ser el único lugar que lo haga, y esa clave debe quedarse en las variables de entorno del servidor. Esa responsabilidad es del dueño de la app, no de la plataforma.

¿Cómo configuro la URL del endpoint de webhook de Stripe?

La configuración es corta una vez que el endpoint existe:

  1. Despliega el handler y anota su URL HTTPS pública.
  2. En el dashboard de Stripe, abre la sección de desarrolladores, luego webhooks, y agrega un endpoint con esa URL.
  3. Selecciona solo los eventos que tu app maneja. Empieza con pocos, por ejemplo las checkout sessions completadas, y agrega más de forma deliberada.
  4. Copia el secreto de firma que Stripe muestra para ese endpoint y guárdalo como variable de entorno del servidor.
  5. En el handler, verifica la firma con la librería de Stripe antes de leer cualquier otra cosa.
  6. Devuelve una respuesta 2xx rápido, en cuanto el evento quede registrado de forma segura.

El modo de prueba y el modo live tienen endpoints y secretos de firma separados. Un bug común en el lanzamiento es desplegar con el secreto de prueba todavía configurado, de modo que todos los eventos reales se rechazan. Revisa esto antes de salir a producción.

Haz el trabajo lento, como enviar correos, después de responder o en una cola. Si tu handler tarda demasiado, Stripe lo trata como un fallo y reintenta.

¿Qué pasa si el webhook falla o llega dos veces?

Según su documentación, Stripe reintenta las entregas fallidas durante un período, con intervalos cada vez mayores. Así que una caída temporal de tu lado suele ser recuperable, si tu endpoint está construido para manejarlo. Consulta la documentación actual de Stripe para conocer la ventana exacta de reintentos.

Los reintentos también implican que el mismo evento puede llegarte más de una vez. Los eventos también pueden llegar fuera de orden: una actualización puede aparecer antes que el evento de creación que esperabas.

Prepárate para estos casos:

  • Endpoint caído: Stripe reintenta, y puedes reenviar los eventos desde el dashboard cuando corrijas el bug.
  • El handler lanza un error: devuelve un estado distinto de 2xx para que Stripe reintente, pero asegúrate de que no quede trabajo a medias.
  • Entrega duplicada: detéctala y no hagas nada la segunda vez.
  • Eventos fuera de orden: confía en el estado actual del objeto en Stripe, no en la secuencia de llegada.

Agrega también el hábito de la conciliación. Un job programado o una revisión manual que compare los pagos recientes en Stripe con tus pedidos detectará los eventos que se perdieron para siempre.

Ilustración de un mensaje duplicado entregado dos veces y procesado una sola vez, que representa el manejo idempotente de webhooks
Una entrega duplicada debe reconocerse y dejarse a un lado, no procesarse de nuevo.

¿Cómo evito los pedidos duplicados?

La técnica se llama idempotencia: procesar la misma entrada dos veces tiene el mismo resultado que procesarla una vez. En la práctica:

  1. Crea una tabla de IDs de eventos de Stripe procesados, con una restricción de unicidad.
  2. Cuando llegue un evento, intenta insertar su ID. Si la inserción falla porque ya existe, devuelve 200 y detente.
  3. Guarda también en el pedido el ID de la checkout session o del payment intent, con su propia restricción de unicidad, para que dos eventos distintos sobre el mismo pago no creen dos pedidos.
  4. Haz la inserción y la actualización del pedido en una sola transacción de base de datos.

La restricción de unicidad importa más que una verificación “si existe” en el código. Dos entregas pueden llegar casi en el mismo instante, ambas pasan la verificación y ambas crean un pedido. La base de datos es el único lugar que puede decir que no de forma confiable.

Los pedidos dobles también vienen del lado del navegador: un cliente que recarga la página de éxito, o una app que crea un pedido antes del pago y otro después. Mantén un solo registro de pedido, creado una vez, y deja que el webhook lo pase a pagado.

¿Puede un escáner de seguridad detectar bugs de webhooks?

En general, no. Un escáner genérico puede encontrar una clave expuesta o un puerto abierto. No sabe que tu página de éxito desbloquea contenido, que tu handler se salta la verificación de firma por comodidad ni que dos eventos crean dos pedidos. Esos son problemas de lógica de negocio y están en cómo está diseñado tu flujo de pago.

Pedirle al constructor con IA que revise su propio trabajo tiene un límite parecido. En mi experiencia, las reglas de datos, las claves y los flujos de pago son justo lo que estas herramientas suelen hacer mal, y un mensaje seguro de “corregido” no es evidencia.

Antes de que lleguen usuarios reales, cuatro revisiones cubren la mayor parte del riesgo: reglas de acceso a datos, claves expuestas, confirmación de pagos y backups. El webhook es la revisión de confirmación de pagos. Para probarlo, haz una compra real en modo de prueba y luego intenta romperlo: reenvía el evento, envía uno con una firma inválida, detén el endpoint y reenvía, y visita la URL de éxito sin pagar. Cada caso debe terminar de forma segura.

¿Quieres una segunda opinión sobre tu flujo de pago?

Si tu app construida con IA recibe pagos y no estás seguro de que el webhook esté bien, escríbeme a me@filipeeduardo.dev con tu configuración y te digo qué revisaría primero.

Preguntas frecuentes

¿Necesito un webhook si uso Stripe Checkout o Payment Links?

Sí, si tu app debe reaccionar a los pagos, por ejemplo dando acceso o registrando pedidos. La página alojada recibe el pago, pero solo un webhook se lo comunica a tu backend de forma confiable.

¿Puedo poner el handler del webhook en el código del frontend?

No. Necesita claves secretas y debe ser accesible para los servidores de Stripe. Usa una función de servidor o una ruta de backend.

¿Debo devolver un error si recibo un evento que no manejo?

En general, devuelve 200 para los eventos válidos que no manejas, para que Stripe no siga reintentándolos. Mejor aún, suscríbete solo a los eventos que usas.

¿Basta con el webhook o también debo consultar la API de Stripe?

El evento verificado suele bastar. Para acciones de alto valor, obtener el objeto desde Stripe antes de entregar el pedido agrega una verificación de seguridad extra y barata.

¿Esto es asesoría legal o fiscal?

No. Solo cubre la práctica técnica. Para cumplimiento normativo de pagos, reembolsos o temas fiscales, habla con un abogado o un contador.

stripe webhook

Filipe Eduardo

Ingeniero de Software Sénior. Siete años creando productos web y móviles de punta a punta y liderando los equipos que los entregan.

Sígueme

¿Quieres hablar
de esto?

Trabajo en problemas así todos los días. Cuéntame el tuyo.

Contáctame