Depurar webhooks de Stripe: Solución a "signature verification failed"
Keyword objetivo: depurar webhooks stripe firma hmac timestamp raw body
¿Recibes el error "signature verification failed" al procesar webhooks de Stripe? Este error indica que Stripe no puede verificar la firma de tu webhook. La causa principal suele ser un manejo incorrecto del cuerpo (raw body) o del timestamp. Este artículo te guiará paso a paso para identificar y solucionar el problema, asegurando la correcta recepción y procesamiento de tus eventos de Stripe.
¿Por qué falla la verificación de firma en los webhooks de Stripe?
Stripe utiliza un proceso robusto para asegurar la integridad y autenticidad de sus webhooks. La firma de cada webhook se genera combinando el timestamp y el payload (cuerpo) del evento utilizando una clave secreta. Si cualquiera de estos componentes se altera durante la transmisión o procesamiento, la verificación de la firma fallará. Los errores más comunes que provocan este fallo son:
- Manipulación del cuerpo del webhook: La modificación del cuerpo del webhook, por ejemplo, mediante la serialización incorrecta del JSON, rompe la firma.
- Manejo incorrecto del timestamp: Un timestamp incorrecto o desactualizado puede causar fallos de verificación.
- Uso del secreto incorrecto: Usar el secreto de prueba en producción o viceversa es un error frecuente.
- Errores en el código de verificación: Implementación incorrecta de la lógica de verificación de la firma.
Paso a paso: Cómo depurar y solucionar "signature verification failed"
A continuación, te presentamos una guía práctica para solucionar el error "signature verification failed" en tus webhooks de Stripe. Sigue estos pasos para identificar y corregir el problema:
1. Verifica el Secret Key de tu Webhook
Acción: Confirma que estás usando el secret key correcto para el webhook específico y el entorno (test o live). El secret key es único por endpoint y entorno.
Cómo hacerlo:
- Panel de Stripe: Navega a la sección de webhooks en el Dashboard de Stripe.
- Selecciona el endpoint: Elige el webhook específico que está fallando.
- Verifica el secret: En la configuración del endpoint, encontrarás el secret key. Asegúrate de que coincide con el que estás utilizando en tu código.
- Ambiente: Confirma que el secret key es el correcto para el entorno (test o live) en el que estás operando.
2. Captura el Raw Body sin Modificar
Acción: Asegúrate de que el cuerpo del webhook (raw body) no sea modificado antes de que tu aplicación verifique la firma. Cualquier alteración en el formato o contenido del cuerpo invalidará la firma.
Cómo hacerlo:
- Frameworks: Usa el middleware correcto para capturar el raw body sin parsear el JSON.
- Express (Node.js): Usa
express.raw({type: 'application/json'}). Este middleware permite acceder al cuerpo del request sin que Express lo parsee automáticamente. - PHP: Utiliza
file_get_contents("php://input")para leer el raw body. - Otros frameworks: Consulta la documentación de tu framework para encontrar la manera de acceder al raw body. Busca opciones para deshabilitar el parsing automático del cuerpo del request.
Ejemplo (Node.js con Express):
import express from 'express';
import Stripe from 'stripe';
const app = express();
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
let event;
try {
event = Stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
console.error(`⚠️ Webhook signature verification failed.`, err);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Maneja el evento...
res.status(200).send('success');
});
3. Entiende y Valida la Cabecera Stripe-Signature
Acción: La cabecera Stripe-Signature contiene la información necesaria para verificar la firma del webhook. Esta cabecera incluye el timestamp y una o más firmas HMAC. Debes calcular el HMAC sobre la concatenación del timestamp y el payload (${timestamp}.${payload}).
Cómo hacerlo:
- Parseo: Extrae el timestamp y las firmas de la cabecera
Stripe-Signature. Utiliza la funciónStripe.webhooks.constructEvent()del SDK de Stripe para simplificar este proceso. - Validación del timestamp: Verifica que el timestamp no sea demasiado antiguo. Stripe recomienda una tolerancia de unos pocos minutos para evitar problemas de sincronización de reloj.
- Implementación HMAC: Si no utilizas el SDK de Stripe, deberás calcular el HMAC (SHA256) del string
{timestamp}.{rawBody}utilizando tu secret key.
4. Implementa la Verificación de Firma en tu Código
Acción: Utiliza el SDK de Stripe (recomendado) o implementa la lógica de verificación de firma de forma precisa. Si usas el SDK, la función Stripe.webhooks.constructEvent() simplifica el proceso.
Cómo hacerlo (usando el SDK de Stripe):
- Obtén el
rawBodydel request. - Obtén la cabecera
stripe-signaturedel request. - Obtén el
endpointSecretdel Dashboard de Stripe. - Llama a
Stripe.webhooks.constructEvent(rawBody, signature, endpointSecret). Esta función lanzará un error si la verificación falla.
Ejemplo (PHP - concepto):
<?php
$payload = file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$endpoint_secret = 'whsec_xxxxxxxxxxxxxxxxxxxxxxxx';
$event = null;
try {
$event = \Stripe\Webhook::constructEvent(
$payload, $sig_header, $endpoint_secret
);
} catch(\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch (\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}
// Handle the event
// ...
?>
5. Prueba tu Implementación con Stripe CLI
Acción: Utiliza la Stripe CLI para simular webhooks y probar tu implementación de forma local. Esto te permite detectar errores antes de desplegar tu código en producción.
Cómo hacerlo:
- Instala la Stripe CLI: Sigue las instrucciones de Stripe para instalar la CLI en tu sistema.
- Configura la CLI: Autentícate con tu cuenta de Stripe para que la CLI pueda acceder a tus claves.
- Reenvía los webhooks localmente: Utiliza el comando
stripe listen --forward-to localhost:3000/webhook(ajusta el puerto y la ruta según tu configuración). Esto reenviará los webhooks de tu cuenta de Stripe a tu servidor local. - Genera eventos de prueba: Realiza acciones en tu cuenta de Stripe (por ejemplo, crear un pago) para generar webhooks de prueba.
- Verifica la salida de la CLI: La CLI mostrará información sobre la recepción y el procesamiento de los webhooks, incluyendo cualquier error en la verificación de la firma.
Checklist: Pasos clave para la verificación de webhooks en producción
Para asegurar una implementación robusta y segura de tus webhooks de Stripe, sigue esta lista de verificación antes de desplegar tu código en producción:
- Secret Key Correcto: Verifica que utilizas el secret key correcto (test o live) para el entorno.
- Raw Body sin Modificar: Asegúrate de capturar el raw body sin que sea parseado o modificado por middleware o bibliotecas.
- Validación del Timestamp: Implementa la validación del timestamp con una tolerancia razonable (p. ej., 5 minutos) para evitar ataques de replay.
- Verificación de Firma: Utiliza el SDK de Stripe o implementa la verificación de la firma de forma precisa, siguiendo las guías de Stripe.
- Manejo de Errores: Implementa un manejo de errores adecuado para los fallos de verificación de la firma, incluyendo logging y notificaciones.
- Idempotencia: Implementa la idempotencia para evitar el procesamiento duplicado de eventos.
- Pruebas Rigurosas: Realiza pruebas exhaustivas, incluyendo pruebas locales con Stripe CLI y pruebas en entornos de staging.
Errores comunes y cómo evitarlos
Aquí hay algunos errores comunes que causan fallos en la verificación de la firma de los webhooks de Stripe, y cómo puedes evitarlos:
1. Serialización del JSON
Error: Si tu código serializa el JSON del cuerpo del webhook (por ejemplo, para "embellecerlo"), la firma no coincidirá porque el orden de los campos y los espacios pueden cambiar.
Solución: Asegúrate de acceder al raw body (sin parsear) y utilizarlo directamente para la verificación de la firma. No intentes modificar el cuerpo antes de verificarlo.
2. Leer el Body Dos Veces
Error: Algunos frameworks o middleware pueden consumir el stream del cuerpo del request (body) una vez. Si lo lees una vez para propósitos de logging o depuración, y luego de nuevo para la verificación de la firma, el segundo intento fallará.
Solución: Asegúrate de leer el body solo una vez y de utilizarlo tanto para la verificación de la firma como para el procesamiento del evento. Si necesitas acceder al body en múltiples lugares, considera almacenarlo en una variable después de la primera lectura.
3. No Comparar en Tiempo Constante
Error: Al comparar la firma calculada con la firma proporcionada en la cabecera, es crucial utilizar una función de comparación en tiempo constante. De lo contrario, podrías ser vulnerable a ataques de timing, donde un atacante puede deducir información sobre la firma basándose en el tiempo que tarda la comparación en completarse.
Solución: Utiliza una función de comparación de strings segura que compare los strings carácter por carácter sin revelar información de tiempo.
4. Aceptar Cualquier Timestamp
Error: Si no validas el timestamp, puedes ser vulnerable a ataques de replay, donde un atacante reutiliza un webhook antiguo. Esto podría llevar a acciones no deseadas en tu aplicación.
Solución: Verifica el timestamp en la cabecera Stripe-Signature y rechaza los eventos que tengan un timestamp demasiado antiguo. Implementa una tolerancia razonable (por ejemplo, 5 minutos) para tener en cuenta posibles desincronizaciones de reloj.
5. No Manejar la Rotación de Secretos
Error: Stripe puede rotar los secret keys por razones de seguridad. Si tu aplicación no está preparada para manejar un cambio en el secret key, la verificación de la firma fallará después de la rotación.
Solución: Diseña tu aplicación para que pueda manejar múltiples secret keys simultáneamente. Stripe recomienda la rotación gradual de secretos para minimizar el impacto. Asegúrate de actualizar tus secretos en tu código y en tu configuración de forma segura y oportuna.
Preguntas frecuentes sobre la depuración de webhooks de Stripe
¿Por qué la firma coincide en local pero no en producción?
La causa más común es una diferencia en la configuración entre tu entorno local y el de producción. Verifica los siguientes puntos:
- Secret Key: ¿Estás usando el secret key correcto (test o live) en cada entorno?
- Parsing del cuerpo: ¿Algún middleware o biblioteca en producción está modificando el cuerpo del webhook antes de que se verifique la firma?
- Versiones de dependencias: Asegúrate de que las versiones del SDK de Stripe y otras dependencias relevantes sean consistentes entre los entornos.
¿Es posible verificar la firma de un webhook sin usar el SDK de Stripe?
Sí, es posible, pero requiere una implementación precisa de la lógica de verificación de la firma. Debes:
- Parsear la cabecera
Stripe-Signaturepara extraer el timestamp y las firmas. - Calcular el HMAC-SHA256 del string
{timestamp}.{rawBody}usando tu secret key. - Comparar la firma calculada con las firmas proporcionadas en la cabecera, utilizando una comparación en tiempo constante.
Recomendamos utilizar el SDK de Stripe para reducir la posibilidad de errores y simplificar el proceso.
¿Cómo puedo manejar eventos duplicados en mis webhooks?
Los webhooks pueden ser enviados más de una vez, por lo que es importante implementar la idempotencia en el procesamiento de tus eventos. Esto significa que tu aplicación debe ser capaz de manejar un mismo evento varias veces sin causar efectos secundarios no deseados.
Solución: Utiliza el event.id de Stripe como identificador único para cada evento. Cuando recibas un webhook, verifica si ya has procesado un evento con ese ID en tu base de datos. Si es así, simplemente ignora el evento (o registra el reintento). Si aún no has procesado el evento, procesa el evento y guarda su ID en tu base de datos para prevenir el procesamiento duplicado en el futuro.
¿Cómo puedo probar mis webhooks localmente?
La manera más efectiva es utilizando la Stripe CLI. Sigue estos pasos:
- Instala y configura la Stripe CLI.
- Ejecuta el comando
stripe listen --forward-to localhost:3000/webhook(ajustando el puerto y la ruta según sea necesario). - Realiza acciones en tu cuenta de Stripe (ej. crear un pago) para generar webhooks.
- Observa la salida de la CLI para confirmar que los webhooks se están recibiendo correctamente y que tu código los está procesando sin errores.
Recomendación final: ¿Qué hacer según tu caso de uso?
La solución al error "signature verification failed" dependerá de la etapa de desarrollo y las necesidades de tu proyecto.
Para Desarrolladores en Etapa de Prueba
Enfoque: Prioriza la configuración y el uso de la Stripe CLI para simular eventos localmente. Verifica que el secret key y el manejo del raw body sean correctos. Usa el SDK de Stripe para simplificar la verificación de la firma.
Para Proyectos en Producción
Enfoque: Asegúrate de que el código esté correctamente configurado para el entorno de producción (secret keys, URLs de webhook, etc.). Implementa un monitoreo robusto de tus webhooks (logs, alertas) para detectar rápidamente cualquier fallo en la verificación de la firma. Revisa el checklist para asegurar una configuración óptima.
Para Proyectos con Integraciones Complejas
Enfoque: Considera la posibilidad de usar una herramienta de gestión de webhooks para simplificar la depuración y el monitoreo de los webhooks de Stripe. Implementa la idempotencia para manejar los reintentos de los webhooks y garantizar la consistencia de los datos. Documenta exhaustivamente la configuración de los webhooks y los pasos de verificación de la firma.