¿Configurando Stripe con IA? 7 Problemas Que Tu Asistente de IA No Puede Resolver
La IA escribe tu webhook handler, pero no puede navegar el Dashboard, encontrar tu signing secret, ni arreglar una URL mal puesta. Estos son los 7 problemas de configuracion que solo tu puedes resolver.
Rod
Founder & Developer
Herramientas de IA como Cursor, Claude y Copilot son muy buenas generando webhook handlers de Stripe. El codigo que producen generalmente funciona. El problema es todo lo que rodea al codigo — la configuracion del Stripe Dashboard, las variables de entorno, las URLs de los endpoints, las API keys. La IA no puede hacer click en botones de tu Stripe Dashboard. No puede verificar si tus env vars estan en Vercel. No puede ver los logs de entregas del webhook y decirte que la respuesta fue un 307.
Construimos todo el sistema de pagos de Data Hogo con asistencia de IA. El codigo estuvo solido desde el dia uno. Aun asi rompimos los pagos de 7 formas diferentes — todos problemas de configuracion que ninguna IA podria haber prevenido o diagnosticado.
Aqui esta cada problema, el error exacto que vas a ver, y donde arreglarlo.
Problema #1: URL del Webhook Endpoint Incorrecta
Esta es la falla mas comun de Stripe webhooks, y la que las herramientas de IA nunca van a detectar porque es un problema de configuracion del Dashboard.
Que pasa: Registras un webhook endpoint en el Stripe Dashboard. Pero la URL no coincide exactamente con lo que tu servidor sirve. Diferencias comunes:
https://datahogo.com/...vshttps://www.datahogo.com/...(redirect de www)https://myapp.com/api/webhookvshttps://myapp.com/api/webhook/(trailing slash)https://myapp.com/api/webhooks/stripevshttps://myapp.vercel.app/api/webhooks/stripe(dominio equivocado)
Que vas a ver en el Stripe Dashboard:
Ve a Developers > Webhooks > selecciona tu endpoint > Recent deliveries. Haz click en cualquier entrega fallida. La respuesta va a verse asi:
{
"redirect": "https://www.datahogo.com/api/webhooks/stripe",
"status": "307"
}HTTP status code: 307 (o 301, 302). Stripe no sigue redirects. Cualquier respuesta que no sea 2xx = entrega fallida.
Como arreglarlo:
- Abre tu app en produccion en el navegador y fijate en la barra de URL — tiene
www? Tiene trailing slash? - Ve a Stripe Dashboard > Developers > Webhooks
- Click en tu endpoint, luego Update details
- Cambia la URL para que coincida exactamente con tu URL de produccion
Tu asistente de IA escribio un handler perfecto. Simplemente nunca recibio una sola request porque la URL estaba mal.
Problema #2: Donde Encontrar Tus API Keys (La Busqueda del Tesoro en el Dashboard)
La IA va a generar codigo con process.env.STRIPE_SECRET_KEY y process.env.STRIPE_WEBHOOK_SECRET, pero no te puede decir de donde sacar esos valores. Aqui la navegacion exacta:
STRIPE_SECRET_KEY y NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
Stripe Dashboard > Developers > API keys
| Key | Empieza con | Usar en |
|---|---|---|
| Publishable key | pk_live_ o pk_test_ |
Frontend (seguro de exponer) |
| Secret key | sk_live_ o sk_test_ |
Backend solamente (nunca exponer) |
Click en "Reveal live key" o "Reveal test key" para copiar. Si nunca lo has revelado, puede que necesites recrearlo.
STRIPE_WEBHOOK_SECRET
Stripe Dashboard > Developers > Webhooks > click en tu endpoint > Signing secret > Reveal
Empieza con whsec_.
Hay TRES secrets de webhook diferentes:
| Entorno | Donde obtenerlo | Notas |
|---|---|---|
| Local dev | Ejecuta stripe listen --forward-to localhost:3000/api/webhooks/stripe — el CLI imprime un secret whsec_ |
Cambia cada vez que reinicias el CLI |
| Test mode | Stripe Dashboard (cambia a Test mode) > Webhooks > endpoint > Signing secret | Persistente |
| Production | Stripe Dashboard (Live mode) > Webhooks > endpoint > Signing secret | Persistente |
Mezclarlos siempre resulta en un 401 Invalid signature sin ningun otro mensaje util. Si tu webhook funciona en local pero falla en produccion, casi seguro es por esto.
Problema #3: Variables de Entorno que No Estan en Produccion
Tu IA genero el codigo. Probaste en local. Funciona. Haces deploy a Vercel. Cada webhook devuelve 401.
Por que: Agregaste STRIPE_WEBHOOK_SECRET en tu .env.local pero nunca lo pusiste en tu hosting.
Que vas a ver en el Stripe Dashboard: Cada entrega muestra HTTP status 401 con respuesta "Invalid signature".
Como arreglarlo en Vercel:
- Vercel Dashboard > tu proyecto > Settings > Environment Variables
- Agrega
STRIPE_WEBHOOK_SECRETcon el valor del Stripe Dashboard (live mode) - Asegurate de que "Production" este seleccionado (no solo Preview/Development)
- Redeploya — las env vars no se aplican a deployments existentes
Lista completa de env vars de Stripe que necesitas en produccion:
| Variable | Requerida | Donde obtenerla |
|---|---|---|
STRIPE_SECRET_KEY |
Si | Developers > API keys > Secret key |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Si | Developers > API keys > Publishable key |
STRIPE_WEBHOOK_SECRET |
Si | Developers > Webhooks > endpoint > Signing secret |
Si no estas seguro de cuales env vars olvidaste poner en produccion, pasa mas seguido de lo que crees. Nuestra guia de exposicion de archivos env explica por que el manejo de .env es una fuente constante de incidentes en produccion.
Problema #4: Conflicto de Version de API — RangeError: Invalid time value
Este es el mas traicionero. Todo funciona bien por semanas, y un dia tu webhook crashea con un RangeError y no tienes idea de por que.
Que pasa: Stripe evoluciona su API. Tu codigo (probablemente generado por IA) fue escrito para una version de la API, pero Stripe manda los eventos webhook usando la version configurada en tu cuenta del Dashboard. Cuando campos se mueven entre objetos en una nueva version, tu codigo lee undefined donde esperaba datos.
Ejemplo real que nos paso:
Nuestro codigo usaba API version 2024-12-18.acacia:
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-12-18.acacia",
});El Dashboard de Stripe estaba en 2025-10-29.clover. En esta version, current_period_start y current_period_end se movieron del objeto Subscription a SubscriptionItem (dentro de items.data[0]).
Nuestro handler hacia:
// Esto funciono por meses, luego de repente se rompio
const periodStart = new Date(subscription.current_period_start * 1000).toISOString();Despues del cambio de version de API:
subscription.current_period_startesundefinedundefined * 1000=NaNnew Date(NaN).toISOString()lanzaRangeError: Invalid time value
La solucion: Leer de ambas ubicaciones:
const firstItem = subscription.items?.data?.[0] as any;
const periodStart =
(subscription as any).current_period_start ?? firstItem?.current_period_start;
const periodEnd =
(subscription as any).current_period_end ?? firstItem?.current_period_end;
if (periodStart) {
const startDate = new Date(periodStart * 1000).toISOString();
// usar startDate...
}Como verificar tu version de API:
- Tu codigo: busca el
apiVersionen la inicializacion de tu cliente Stripe - Tu Dashboard: Stripe Dashboard > Developers > Overview muestra "API version" arriba
- Los eventos webhook: cada payload incluye
"api_version"— revisa una entrega reciente
Si no coinciden, eventualmente vas a encontrar breaking changes. La IA no detecta esto porque no sabe que version de API tiene tu Dashboard.
Problema #5: No Loggear Errores del Webhook
Los webhook handlers generados por IA generalmente tienen manejo basico de errores, pero rara vez loggean suficiente detalle para debuggear en produccion.
Lo que la IA tipicamente genera:
try {
event = stripe.webhooks.constructEvent(body, sig!, secret);
} catch {
return new Response("Invalid signature", { status: 401 });
}Esto captura el error pero no te dice nada. El secret estaba mal? El body ya se parseo antes? Faltaba el header?
Lo que realmente necesitas:
try {
event = stripe.webhooks.constructEvent(body, sig!, secret);
} catch (err) {
console.error("[stripe-webhook] Verificacion de firma fallo:",
err instanceof Error ? err.message : err
);
return new Response("Invalid signature", { status: 401 });
}Mensajes de error comunes de Stripe y que significan:
| Mensaje de error | Causa |
|---|---|
No signatures found matching the expected signature for payload |
STRIPE_WEBHOOK_SECRET incorrecto o el body se parseo con req.json() antes de verificar |
Timestamp outside the tolerance zone |
Problema de sincronizacion de reloj o el evento es muy viejo |
Webhook payload must be provided as a string |
Pasaste JSON parseado en vez de texto crudo |
Lo mismo aplica para el handler principal — envuelve todo en un try/catch y loggea con contexto:
try {
await handleStripeEvent(event);
} catch (err) {
console.error("[stripe-webhook] Procesamiento fallo:", {
eventId: event.id,
eventType: event.type,
error: err instanceof Error ? err.message : err,
});
// Agrega Sentry si lo tienes
Sentry.captureException(err, {
extra: { eventType: event.type, eventId: event.id },
});
return new Response("Processing error", { status: 500 });
}Si tu postura de seguridad necesita una segunda opinion, escanea tu repo con Data Hogo — detectamos patrones de manejo de errores faltantes en rutas de webhook entre otras cosas.
Problema #6: Leer los Logs de Webhook en Stripe
Cuando algo sale mal, la respuesta casi siempre esta en los logs del Stripe Dashboard. Pero la mayoria de los developers no saben donde buscar o que significan los status codes.
Donde revisar:
Stripe Dashboard > Developers > Webhooks > selecciona tu endpoint > Recent deliveries
Cada entrega muestra:
- HTTP status code — la respuesta de tu servidor
- Response body — lo que tu servidor devolvio
- Request body — el payload completo del evento que Stripe mando
Que significan los status codes:
| Status | Que paso | Donde esta el problema |
|---|---|---|
| 200 | Exito | Nada — funciono |
| 307 (o 301, 302) | Tu URL redireciono | Dashboard: URL del endpoint incorrecta. Solucion: actualizar la URL del webhook |
| 401 | La verificacion de firma fallo | STRIPE_WEBHOOK_SECRET faltante o incorrecto en tu entorno |
| 404 | Ruta no encontrada | Path incorrecto en la URL del endpoint, o la ruta no existe en tu deployment |
| 500 | Tu handler crasheo | Excepcion no manejada en tu codigo — revisa los logs de tu servidor |
| Timeout | El handler tardo demasiado | Tu handler esta haciendo demasiado trabajo de forma sincrona |
Donde encontrar eventos individuales:
Stripe Dashboard > Developers > Events
Aqui puedes filtrar por tipo de evento, ver todos los intentos de entrega, y hacer Resend de eventos que fallaron. Despues de arreglar un bug, ven aqui y reenvia los eventos fallidos uno por uno.
La IA puede escribir tu handler, pero no puede leer estos logs por ti. Cuando algo se rompe, aqui es donde empiezas.
Problema #7: Comportamiento de Reintentos de Stripe (Que Pasa Despues de una Falla)
Cuando una entrega de webhook falla, Stripe no se rinde. Reintenta con backoff exponencial durante 3 dias:
- Inmediatamente
- ~5 minutos despues
- ~30 minutos
- ~2 horas
- ~5 horas
- ~10 horas
- (sigue espaciandose)
Que significa para ti:
- No entres en panico. Tienes 3 dias para arreglar un webhook roto antes de que Stripe deje de intentar.
- No hagas deploy de un fix a medias. Tomate el tiempo para encontrar la causa real.
- Despues de arreglar, reenvia los eventos fallidos desde Developers > Events > selecciona evento > Resend.
- Si Stripe deshabilito tu endpoint (despues de 3 dias de fallas), reactivalo desde Developers > Webhooks > selecciona endpoint.
Stripe tambien te manda un email cuando un webhook endpoint empieza a fallar consistentemente. Revisa tu inbox — a nosotros nos llego con el asunto "Action required: webhook endpoint is failing" con el conteo exacto de fallas y la fecha.
Template Completo del Webhook Handler
Aqui esta un webhook handler production-ready para Next.js App Router que maneja los 7 problemas:
// app/api/webhooks/stripe/route.ts
import type Stripe from "stripe";
import * as Sentry from "@sentry/nextjs";
import { constructWebhookEvent } from "@/lib/stripe/client";
export async function POST(req: Request) {
// 1. Leer body crudo — NUNCA uses req.json() (rompe la verificacion de firma)
const body = await req.text();
const signature = req.headers.get("stripe-signature");
if (!signature) {
return Response.json({ error: "Missing signature" }, { status: 401 });
}
// 2. Verificar firma — LOGGEAR el error, no tragarselo
let event: Stripe.Event;
try {
event = constructWebhookEvent(body, signature);
} catch (err) {
console.error("[stripe-webhook] Verificacion de firma fallo:",
err instanceof Error ? err.message : err
);
return Response.json({ error: "Invalid signature" }, { status: 401 });
}
// 3. Try/catch global — nunca dejar que una excepcion devuelva 500 sin control
try {
await handleStripeEvent(event);
} catch (err) {
console.error("[stripe-webhook] Error no manejado:", {
eventType: event.type,
eventId: event.id,
error: err instanceof Error ? err.message : err,
});
Sentry.captureException(err, {
extra: { eventType: event.type, eventId: event.id },
});
return Response.json({ received: true, error: "Processing error" });
}
return Response.json({ received: true });
}
async function handleStripeEvent(event: Stripe.Event): Promise<void> {
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
const userId = session.metadata?.userId;
const plan = session.metadata?.plan;
if (!userId || !plan) break;
// activar suscripcion...
break;
}
case "customer.subscription.updated": {
const subscription = event.data.object as Stripe.Subscription;
// 4. Seguro entre versiones de API — maneja migracion de campos
const firstItem = subscription.items?.data?.[0] as any;
const periodStart =
(subscription as any).current_period_start ?? firstItem?.current_period_start;
const periodEnd =
(subscription as any).current_period_end ?? firstItem?.current_period_end;
if (periodStart && periodEnd) {
const startDate = new Date(periodStart * 1000).toISOString();
const endDate = new Date(periodEnd * 1000).toISOString();
// actualizar periodos de suscripcion...
}
break;
}
case "customer.subscription.deleted": {
// degradar al usuario a plan free...
break;
}
case "invoice.payment_failed": {
// marcar suscripcion como past_due...
break;
}
}
}Checklist pre-deployment:
- URL del webhook en Stripe Dashboard coincide con tu URL exacta de produccion (incluyendo www)
-
STRIPE_SECRET_KEYesta en las env vars de tu hosting (no solo en .env.local) -
STRIPE_WEBHOOK_SECRETesta en las env vars de tu hosting (secret de live mode, no del CLI) - Probaste una entrega de webhook desde el Stripe Dashboard y viste una respuesta 200
- Monitoreo de errores (Sentry o similar) esta conectado para capturar errores del webhook
Preguntas Frecuentes
¿Por que mi Stripe webhook devuelve 307?
La URL de tu endpoint en el Stripe Dashboard no coincide con tu URL de produccion. Stripe no sigue redirects. Ve a Developers > Webhooks, click en una entrega fallida, y mira la respuesta — va a mostrar la URL del redirect. Actualiza la URL de tu endpoint para que coincida exactamente.
¿Donde encuentro mi Stripe webhook signing secret?
Stripe Dashboard > Developers > Webhooks > selecciona tu endpoint > Signing secret > Reveal. Empieza con whsec_. Recuerda: CLI local, test mode, y live mode tienen secrets diferentes.
¿Por que mi Stripe webhook funciona en local pero falla en produccion?
Casi siempre son env vars faltantes. STRIPE_WEBHOOK_SECRET esta en tu .env.local pero no en Vercel/Railway/lo que uses para deploy. Tambien verifica que estas usando el signing secret de produccion (del Dashboard), no el del CLI de stripe listen.
¿Que causa RangeError: Invalid time value en un Stripe webhook?
Conflicto de version de API. Campos como current_period_start se movieron entre objetos en versiones mas recientes de la API de Stripe. Tu codigo lee undefined, multiplica por 1000 para obtener NaN, y new Date(NaN).toISOString() lanza el error. Lee de ambas ubicaciones (vieja y nueva) con un fallback ??.
¿Como reenvio un evento fallido de Stripe webhook?
Stripe Dashboard > Developers > Events > encuentra el evento > Resend. Puedes reenviar eventos individuales despues de arreglar tu endpoint. Stripe reintenta automaticamente por 3 dias antes de darse por vencido.
¿Que API keys de Stripe necesito y donde estan?
Tres keys: (1) STRIPE_SECRET_KEY — Developers > API keys > Secret key (empieza con sk_live_). (2) NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY — misma pagina (empieza con pk_live_). (3) STRIPE_WEBHOOK_SECRET — Developers > Webhooks > endpoint > Signing secret (empieza con whsec_). Nunca expongas la secret key en el frontend.
Posts Relacionados
Cabeceras de Seguridad y SEO: Cómo las Headers Faltantes Afectan tu Ranking
Las cabeceras de seguridad no solo protegen a tus usuarios — su ausencia afecta tu ranking en Google a través de bounce rates, flags de Safe Browsing y páginas rotas.
Tu Archivo .env Está Público — Cómo Descubrirlo y Arreglarlo
¿Tu archivo .env está expuesto en producción? Aprende a detectar variables de entorno expuestas y a arreglarlas antes de que alguien más las encuentre.
OWASP A09 Registro y Monitoreo
OWASP A09 explica por qué las brechas tardan 204 días en detectarse. Aprende qué registrar, qué nunca guardar en logs, y cómo corregir los fallos silenciosos en tu app.