Webhook secret de Stripe: dónde encontrarlo y cómo verificar eventos

Encuentra el webhook secret de Stripe (whsec_), verifica cada evento sobre el cuerpo sin procesar, guárdalo en Supabase o tu hosting y rótalo si se filtra.

Illustration of a webhook envelope with an orange key wax seal being checked at a gate, representing Stripe webhook secret verification
En esta página · 8 secciones
  1. ¿Qué es el webhook secret de Stripe (signing secret)?
  2. ¿Dónde encuentro el webhook secret en el dashboard de Stripe?
  3. ¿Por qué los secrets del modo de prueba y del modo en vivo son distintos?
  4. ¿Cómo funciona la verificación de firmas?
  5. ¿Por qué el secret nunca debe estar en el código del frontend?
  6. ¿Dónde debo guardar el secret en Supabase o en mi propio hosting?
  7. ¿Qué hago si el secret se filtró?
  8. ¿Quieres otra mirada?

Puntos clave

  • El webhook secret (whsec_…) es por endpoint y por modo. No es tu clave de API.
  • Verifica la firma sobre el cuerpo sin procesar de la solicitud antes de confiar en cualquier evento.
  • El modo de prueba y el modo en vivo tienen secrets distintos, así que tu hosting necesita ambos configurados correctamente.
  • Nunca pongas el secret en el código del frontend ni en una variable que el navegador pueda leer.
  • Si se filtra, rota el secret en el dashboard y vuelve a desplegar.
  • El dueño de la app es responsable de las claves y de la confirmación de pagos; Stripe protege su propia infraestructura.

El webhook secret de Stripe es un secret de firma, una cadena que empieza con whsec_, que Stripe usa para firmar cada evento que envía a tu endpoint. Tu servidor usa esa misma cadena para comprobar que el evento realmente vino de Stripe y que no se modificó en el camino.

Si tu app se construyó con una herramienta de IA, este es uno de los primeros lugares que reviso. La confirmación de pagos depende de él, y estas herramientas suelen equivocarse con las claves y los flujos de pago. A continuación: dónde encontrar el secret, cómo funciona la verificación, dónde guardarlo y qué hacer si se filtra.

¿Qué es el webhook secret de Stripe (signing secret)?

Cuando creas un endpoint de webhook en Stripe, Stripe genera un secret de firma para él. Cada evento enviado a ese endpoint lleva un header Stripe-Signature, calculado con ese secret.

Cualquiera puede enviar una solicitud POST a tu URL pública. Sin verificación, alguien podría enviar un evento falso de "pago exitoso" y tu app podría marcar un pedido como pagado. El secret es lo que distingue un evento real de uno falsificado.

¿En qué se diferencia de la clave de API?

  • Clave secreta de API (sk_…): permite que tu servidor llame a Stripe para crear cobros, reembolsos, etc.
  • Clave publicable (pk_…): segura en el navegador, se usa en los formularios de checkout.
  • Secret de firma del webhook (whsec_…): permite que tu servidor verifique los eventos que llegan desde Stripe.

Un error común en el código generado por IA es confundirlas, por ejemplo usando la clave de API donde va el secret de firma.

¿Dónde encuentro el webhook secret en el dashboard de Stripe?

Los nombres exactos de los menús pueden cambiar, así que tómalo como la ruta general:

  1. Abre el dashboard de Stripe y ve a Developers y luego a Webhooks.
  2. Haz clic en el endpoint que apunta a tu app.
  3. Busca la sección "Signing secret" y elige mostrarlo.
  4. Copia el valor que empieza con whsec_.

Si todavía no tienes un endpoint, crea uno con tu URL pública y los eventos que quieras. El secret aparece en cuanto el endpoint existe.

¿Y el secret de la Stripe CLI?

Cuando reenvías eventos a localhost con la Stripe CLI, la CLI imprime su propio valor whsec_. Ese es solo para pruebas locales y es distinto del secret del endpoint en tu dashboard. No lo copies a producción.

Ilustración de un dashboard de pagos con un campo de secret de firma enmascarado junto a un candado
El secret de firma vive en la configuración del endpoint y solo se muestra cuando lo pides.

¿Por qué los secrets del modo de prueba y del modo en vivo son distintos?

Stripe mantiene los datos de prueba y los datos en vivo completamente separados. Cada modo tiene sus propios endpoints, así que cada uno tiene su propio secret de firma. Un evento del modo en vivo firmado con el secret en vivo va a fallar la verificación si tu servidor tiene el secret de prueba, y viceversa.

Síntomas típicos de esta discrepancia:

  • Todo funciona en prueba, pero los pagos reales nunca se confirman.
  • Aparecen errores de verificación justo después del lanzamiento.
  • Los pedidos se quedan "pendientes" aunque Stripe los muestre como pagados.

Antes de salir en vivo, comprueba que el hosting de producción tenga el secret en vivo y la clave de API en vivo, y que la URL del endpoint en vivo esté registrada en el modo en vivo. Es fácil pasarlo por alto cuando un constructor de apps con IA conectó todo en modo de prueba.

¿Cómo funciona la verificación de firmas?

El header Stripe-Signature contiene un timestamp y una o más firmas. Stripe calcula un HMAC del timestamp más el cuerpo sin procesar exacto usando tu secret. Tu servidor repite el cálculo y compara los resultados.

No deberías escribir esto tú mismo. Las bibliotecas oficiales de Stripe tienen una función (en Node, stripe.webhooks.constructEvent) que recibe el cuerpo sin procesar, el header y el secret, y lanza un error si algo está mal. También rechaza timestamps demasiado antiguos, lo que ayuda contra eventos reenviados (replay).

La trampa del cuerpo sin procesar

Un bug con el que me encuentro a menudo: el framework parsea el JSON primero y luego el código lo vuelve a serializar para la verificación. Incluso un cambio de espacios en blanco rompe la firma. Tienes que pasar los bytes sin procesar, intactos. En los route handlers de Next.js, lee el texto de la solicitud; en Express, usa un parser de cuerpo sin procesar solo en esa ruta.

Qué hacer cuando la verificación falla

  • Responde con un 400 y no hagas nada más con el payload.
  • No desactives la verificación para "hacer que funcione". Eso convierte tu endpoint en una puerta abierta.
  • Revisa el secret, el modo y el cuerpo sin procesar, en ese orden.

Un escáner de seguridad genérico no va a detectar un paso de verificación faltante, porque es lógica de negocio. Cuando reviso una app construida con IA, leo el handler del webhook a mano precisamente por esa razón.

Sobre con dos sellos idénticos comparados bajo una lupa, que representa la verificación de firmas
La verificación es una comparación: tu servidor vuelve a calcular la firma y comprueba que coincida.

¿Por qué el secret nunca debe estar en el código del frontend?

Todo lo que se envía al navegador es público. Si el secret está en el código del cliente, en una variable de entorno pública o en un archivo commiteado en un repositorio público, cualquiera puede leerlo y firmar eventos falsos que pasen tu verificación.

En las apps construidas con IA, presta atención a:

  • Variables con prefijos como NEXT_PUBLIC_ o VITE_, que se incluyen en el código del navegador.
  • Secrets pegados en un prompt, que luego terminan en los archivos de código fuente.
  • Un archivo .env commiteado en GitHub.

El handler del webhook debe ejecutarse en un servidor: una ruta de API, una función serverless o un servicio de backend. Las claves en el navegador son responsabilidad del dueño de la app, no de la plataforma.

¿Dónde debo guardar el secret en Supabase o en mi propio hosting?

En Supabase

Si el handler es una Edge Function, guarda el secret con la funcionalidad de secrets de Supabase (el comando de la CLI supabase secrets set o su equivalente en el dashboard) y léelo desde el entorno de la función. Nunca lo escribas en una tabla que el cliente pueda consultar. Confirma también que el endpoint del webhook no exija un token de inicio de sesión de usuario, ya que Stripe no va a enviar uno; la firma es su autenticación.

En tu propio hosting

Usa las variables de entorno o el gestor de secrets de tu hosting (Vercel, Fly.io, AWS y similares tienen uno). Define valores separados para preview, prueba y producción. Mantén el archivo .env fuera del control de versiones con .gitignore.

Un checklist simple

  • Un secret por entorno y por modo.
  • Guardado solo en el servidor.
  • Fuera de logs, mensajes de error y capturas de pantalla.
  • Conocido por la menor cantidad de personas posible.

¿Qué hago si el secret se filtró?

Actúa rápido, pero con calma:

  1. En el dashboard, abre el endpoint y rota el secret de firma. Stripe te permite mantener el anterior válido por un tiempo limitado para que hagas el cambio sin perder eventos.
  2. Actualiza la variable de entorno en tu hosting y vuelve a desplegar.
  3. Elimina el secret del código y, si estaba en un repositorio Git, considéralo expuesto para siempre, porque el historial lo conserva.
  4. Revisa los eventos y pedidos recientes en busca de algo sospechoso, como pedidos pagados sin un pago correspondiente en Stripe.
  5. Si tu clave de API también quedó expuesta, rótala también.

Haz que la entrega de pedidos compruebe el estado del pago en Stripe donde importe, y haz que los handlers sean idempotentes para que un evento duplicado no cree un pedido duplicado. No es una garantía contra el abuso, pero limita el daño.

¿Quieres otra mirada?

Si quieres que alguien revise cómo maneja tu app los eventos de Stripe, escríbeme a me@filipeeduardo.dev con el enlace o el código del handler y te diré qué revisaría primero.

Preguntas frecuentes

¿El webhook secret es lo mismo que mi clave de API de Stripe?

No. La clave de API te permite llamar a Stripe. El webhook secret solo verifica los eventos que Stripe te envía.

¿Puedo tener más de un webhook secret?

Sí. Cada endpoint tiene el suyo, y los modos de prueba y en vivo están separados, así que un proyecto suele tener varios.

¿Puedo saltarme la verificación mientras hago pruebas?

Es mejor no hacerlo. Usa la Stripe CLI para reenviar eventos de prueba reales y firmados, así el camino de código que pruebas es el mismo que publicas.

¿La plataforma protege mi secret por mí?

La plataforma protege su infraestructura. Dónde pones el secret y quién puede leerlo depende de ti como dueño de la app.

¿Con qué frecuencia debo rotarlo?

No hay una regla fija. Rótalo cuando alguien deje el equipo, cuando sospeches una exposición o como parte de tu propia rutina de seguridad.

stripe webhook secret

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