Los webhooks son una de las formas más potentes de recibir actualizaciones en tiempo real de servicios de terceros. Una sola solicitud HTTP POST de Stripe, GitHub, Shopify o Twilio puede activar lógica de negocio crítica en tu aplicación: cobrar a un cliente, actualizar un repositorio, enviar un pedido o enviar un SMS de confirmación.
Pero cada solicitud de webhook llega a través de la internet pública. Y eso significa que cualquiera que adivine o descubra tu URL de webhook puede enviar cargas maliciosas que parecen completamente legítimas. Sin la autenticación adecuada, tu aplicación no tiene forma de distinguir entre un evento real y uno falsificado.
Ahí es donde entra en juego la verificación de la firma del webhook. Es un mecanismo simple y estandarizado que garantiza que cada solicitud de webhook entrante proviene genuinamente del servicio que esperas y no ha sido alterada durante el tránsito.
En esta guía completa, aprenderás exactamente cómo funciona la verificación de la firma del webhook y cómo implementarla correctamente en lenguajes populares. También verás errores comunes a evitar y cómo probar todo de principio a fin, de forma rápida y fiable.
¿Qué es la verificación de la firma de webhook?
La verificación de la firma de webhook es el proceso de confirmar que una solicitud de webhook entrante realmente proviene del servicio que esperas y no ha sido manipulada.
La mayoría de los proveedores utilizan HMAC (código de autenticación de mensajes basado en hash) con SHA-256 o SHA-512. El servicio calcula:
signature = HMAC-SHA256(secret_key, payload)
Luego, envían la firma en un encabezado (normalmente X-Signature, Signature, o X-Hub-Signature-256).
Tu servidor:
- Recibe la carga útil como bytes brutos (¡importante!)
- Vuelve a calcular el HMAC usando tu secreto almacenado
- Compara la firma calculada con la recibida
Si coinciden exactamente, procesas el webhook. De lo contrario, devuelves HTTP 401 o 403.
Por qué HMAC-SHA256 es el estándar de la industria
Los proveedores eligen HMAC-SHA256 por buenas razones:
- Rápido: Incluso en hardware modesto, es increíblemente veloz.
- Seguro: SHA-256 sigue sin ser roto en 2025.
- Simple: Una clave secreta, sin pares de claves públicas/privadas que gestionar.
- Estandarizado: Bibliotecas en todos los lenguajes lo implementan correctamente.
GitHub, Stripe, Shopify, Slack y docenas de otros usan HMAC-SHA256.
Cómo implementar la verificación de la firma de webhook en Node.js
Comencemos con un ejemplo real en Node.js.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const computedSignature = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computedSignature),
Buffer.from(signature)
);
}
Puntos clave a tener en cuenta:
- Usa siempre
crypto.timingSafeEqualpara prevenir ataques de temporización. - Nunca uses
===en cadenas; es vulnerable. - Usa
Buffer.from()para asegurar una comparación de tiempo constante.
Ejemplo de middleware de Express:
app.post('/webhooks/stripe', (req, res, next) => {
const signature = req.headers['stripe-signature'];
const secret = process.env.STRIPE_WEBHOOK_SECRET;
// Obtener cuerpo crudo (Express necesita middleware para preservar el cuerpo crudo)
const rawBody = req.rawBody || req.body; // usar body-parser con la opción verify
if (!verifyWebhookSignature(rawBody, signature, secret)) {
return res.status(401).send('Firma inválida');
}
// La firma es válida → procesar el evento
next();
});
Implementación en Python (FastAPI + Pydantic)
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
app = FastAPI()
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
computed = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
@app.post("/webhooks/github")
async def github_webhook(request: Request):
signature = request.headers.get("X-Hub-Signature-256")
if not signature:
raise HTTPException(status_code=401, detail="Firma faltante")
payload = await request.body()
if not verify_signature(payload, signature.split('=')[1], SECRET):
raise HTTPException(status_code=401, detail="Firma inválida")
# Procesar webhook
return {"status": "ok"}
Errores comunes que cometen los desarrolladores (y cómo evitarlos)
1. Usar JSON.stringify() o el cuerpo parseado
Muchos frameworks parsean JSON automáticamente. Esto rompe la verificación porque los espacios en blanco, el orden de las claves y el formato difieren.
Solución: Siempre captura el cuerpo crudo antes de parsear.
En Express: Usa body-parser con { verify: true }
En FastAPI: Usa await request.body()
2. Comparar cadenas con ===
Los ataques de temporización pueden filtrar información. Usa crypto.timingSafeEqual o hmac.compare_digest.
3. Almacenar secretos en el código
Usa variables de entorno o gestores de secretos (AWS Secrets Manager, HashiCorp Vault, etc.).
4. Olvidar manejar ataques de repetición
La mayoría de los proveedores incluyen una marca de tiempo. Verifica que el evento sea reciente (por ejemplo, dentro de 5 minutos).
const timestamp = req.headers['X-Signature-Timestamp'];
if (Date.now() - timestamp > 5 * 60 * 1000) {
return res.status(401).send('Marca de tiempo demasiado antigua');
}
5. Usar SHA-1 (¡Todavía sucede!)
GitHub dejó de usar SHA-1 en 2022. Usa siempre SHA-256.
Probando la verificación de la firma de webhook con Apidog
Probar webhooks manualmente es tedioso. Envías una solicitud, revisas los registros, corriges, repites.
Apidog lo hace trivial:
En tu proyecto Apidog, haz clic en el icono + en la barra lateral izquierda y elige "New Other Protocol APls" (Nuevas API de otros protocolos) > "Webhook".
Después de crear el Webhook, completa los siguientes campos en el editor:Request Method (Método de solicitud): Típicamente POST.Webhook name (Nombre del Webhook): Aparecerá en la documentación de la API y en la exportación de OpenAPI, por ejemplo, pedido.Debug URL (optional) (URL de depuración (opcional)): La URL real utilizada para enviar solicitudes de prueba. Nota: Esto es solo para fines de prueba y no se incluirá en la documentación.Other Info (Otra información): Como el cuerpo de la solicitud.
Haz clic en Guardar una vez que hayas completado todos los campos requeridos.
Simplemente introduce tu URL de Webhook en el campo Debug URL y luego haz clic en Send para simular una llamada de Webhook.
He ahorrado horas de depuración con el simulador de webhooks de Apidog. Incluso es compatible con el formato exacto stripe-signature de Stripe y el prefijo sha256=... de GitHub.
Ejemplo del mundo real: Verificación de webhooks de Stripe
Stripe utiliza un formato de encabezado especial:
stripe-signature: t=1681234567,v1=abc123...,v0=def456...
Debes:
- Extraer la marca de tiempo
t= - Extraer la firma
v1=(preferida) - Volver a calcular usando
payload + timestamp - Comparar solo la parte
v1
Stripe proporciona bibliotecas oficiales para manejar esta complejidad:
const stripe = require('stripe')('sk_...');
stripe.webhooks.constructEvent(payload, sigHeader, endpointSecret);
Pero comprender el HMAC subyacente es crucial cuando necesitas implementarlo tú mismo.
Temas avanzados: Tolerando múltiples firmas
Algunos proveedores (como Stripe) envían múltiples firmas para compatibilidad con versiones anteriores. Tu código debería:
- Dividir el encabezado por
, - Intentar con cada una
- Aceptar si alguna coincide
Mejores prácticas de seguridad en 2025
- Rota los secretos de webhook cada 90 días.
- Usa secretos de corta duración cuando sea posible.
- Registra las verificaciones fallidas, pero nunca el secreto.
- Limita la tasa de solicitudes (rate-limit) en los puntos finales de webhook para prevenir ataques de fuerza bruta.
- Usa siempre HTTPS.
Conclusión: Pequeño paso de verificación, gran ganancia de seguridad
La verificación de la firma de webhook parece un pequeño detalle. Pero es la diferencia entre una aplicación segura y una que los atacantes pueden explotar trivialmente.
Implémentala correctamente, pruébala a fondo con herramientas como Apidog, y duerme mejor sabiendo que tus integraciones están protegidas.
Descarga Apidog gratis hoy mismo y verifica tu primer webhook en menos de 5 minutos. Es la forma más rápida de probar que tu código realmente funciona.
