HMAC — Cómo los webhooks saben que no estás mintiendo
Cualquier servidor puede enviar una solicitud POST a tu punto de conexión de webhook. Las firmas HMAC son la forma en que los enviadores legítimos demuestran que escribieron el contenido — y cómo tú lo verificas.
Cada vez que Stripe carga una tarjeta, dispara un webhook. Cada vez que GitHub fusiona una solicitud, dispara un webhook. Estas plataformas envían solicitudes POST a una URL que usted les proporcionó — pero aquí está la verdad incómoda: cualquier otra persona puede enviar una solicitud POST a esa misma URL.
Entonces, ¿cómo sabe su servidor que la solicitud realmente proviene de Stripe y no de un atacante que adivinó la URL de su punto final? La respuesta es HMAC — y una vez que lo entienda, verá por qué es el enfoque estándar en todas las plataformas de API serias.
El problema: cualquiera puede enviar un POST a su punto final
Los puntos finales de webhooks son simplemente URLs. Son públicamente accesibles (deben serlo, para que el remitente pueda alcanzarlos) y aceptan solicitudes POST. No hay nada que impida que un agresor malicioso cree un payload falso y lo envíe a su punto final.
Imagina que tu manejador de webhooks hace esto cuando recibe un evento:
if event["type"] == "payment.completed":
fulfill_order(event["data"]["order_id"])
Un atacante que conoce tu URL de punto final podría enviar un evento falso con cualquier ID de pedido que desee. payment.completed Sin verificación, tu servidor aceptaría órdenes que nunca habrían sido pagadas.
Necesitas una forma de verificar que el payload fue escrito por alguien que posee un secreto que ambos comparten — sin transmitir ese secreto en la solicitud.
¿Qué es HMAC?
HMAC significa Código de autenticación de mensaje basado en hash. Es una construcción que combina una función de hash criptográfica (normalmente SHA-256) con una clave secreta para producir una firma. La firma demuestra dos cosas:
- Autenticidad — el mensaje fue creado por alguien que posee la clave secreta
- Integridad — el mensaje no ha sido modificado durante la transmisión
La propiedad clave del HMAC: no puedes producir una firma válida sin conocer el secreto. Y no puedes revertir la firma para recuperar el secreto. Es una prueba unidireccional.
HMAC frente a un hash simple
Un hash simple (como SHA-256) del payload resuelve el problema de integridad, pero no de autenticidad. Un atacante que intercepta un payload válido podría recalcula el hash de un payload modificado. El HMAC mezcla tu clave secreta en cada paso del proceso de hashing, por lo que sin la clave, no puedes producir una firma coincidente incluso si conoces el algoritmo de hash utilizado.
Cómo funciona el HMAC de webhooks
El flujo tiene tres pasos: intercambio de clave, firma y verificación.
Paso 1: Intercambio de clave (ocurre una vez)
Cuando configuras un webhook con una plataforma como Stripe o GitHub, ellos generan un secreto de webhook y te lo muestran una vez. Lo almacenas en el servidor (nunca en código del cliente ni en repositorios públicos). Eso es todo — el secreto nunca viaja por el cable nuevamente.
Paso 2: El remitente firma el payload
Antes de enviar el webhook, la plataforma calcula una firma HMAC sobre el cuerpo original de la solicitud utilizando tu clave compartida:
signature = HMAC-SHA256(secret_key, request_body)
La firma se adjunta luego a la solicitud, normalmente en una cabecera como X-Hub-Signature-256 (GitHub) o Stripe-Signature (Stripe). El cuerpo original del payload viaja en el cuerpo sin cambios.
Paso 3: Verificación al recibir la solicitud
Cuando tu servidor recibe el webhook, recalculas el mismo HMAC utilizando el cuerpo original y tu clave almacenada, luego comparas el resultado con la firma en la cabecera. Si coinciden, el payload es auténtico y no modificado. Si no, lo rechazas.
expected = HMAC-SHA256(your_secret, raw_body)
if not constant_time_equal(expected, header_signature):
return 401
Aviso comparación de tiempo constante — volveremos a explicar por qué eso importa.
Ejemplos del Mundo Real
Stripe
Stripe envía una Stripe-Signature cabecera que contiene un timestamp y una o más firmas:
Stripe-Signature: t=1679000000,v1=abc123...,v0=oldformat...
La firma se calcula sobre timestamp.payload (concatenada con un punto). Incluir el timestamp permite a Stripe defenderse contra ataques de retransmisión — si un atacante captura una solicitud firmada válida y la reenvía más tarde, puedes rechazarla porque el timestamp es demasiado antiguo.
GitHub
GitHub envía una X-Hub-Signature-256 cabecera en el formato sha256=<hex_digest>. La firma es HMAC-SHA256 del cuerpo original utilizando la clave de webhook que configuraste en las configuraciones de tu repositorio.
Shopify
Shopify utiliza una X-Shopify-Hmac-Sha256 cabecera con una firma HMAC-SHA256 codificada en Base64 — mismo concepto, diferente codificación.
Verificación en código
Aquí cómo se ve la verificación en tres lenguajes comunes. El patrón es idéntico — solo difieren las llamadas a la biblioteca.
Pitón
import hmac
import hashlib
def verify_webhook(secret: str, payload: bytes, signature_header: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
received = signature_header.removeprefix("sha256=")
return hmac.compare_digest(expected, received)
Node.js
const crypto = require('crypto');
function verifyWebhook(secret, rawBody, signatureHeader) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const received = signatureHeader.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
}
PHP
function verifyWebhook(string $secret, string $rawBody, string $signatureHeader): bool {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signatureHeader);
}
Errores que rompen la seguridad
1. Usar == en lugar de comparación de tiempo constante
Comparación estándar de cadenas (==, ===) corta el circuito tan pronto como encuentra una desigualdad. Esto crea un canal de tiempo lateral: un atacante puede medir cuánto tiempo tarda tu servidor en rechazar diferentes firmas. Las cadenas que comparten un prefijo más largo tardan ligeramente más en ser rechazadas. Con suficientes solicitudes, un atacante puede usar esto para reconstruir una firma válida byte por byte.
Siempre usa una comparación de tiempo constante: hmac.compare_digest() en Python, crypto.timingSafeEqual() en Node.js, hash_equals() en PHP.
2. Analizar el cuerpo antes de verificar
El HMAC se calcula sobre los bytes brutos del cuerpo de la solicitud. Si analizas el JSON primero y luego lo re-serializas para verificar, podrías obtener una secuencia de bytes diferente (diferente orden de claves, espacios en blanco, codificación). Siempre captura el cuerpo bruto antes que el analizador de cuerpo de tu framework lo toque, luego verifica contra ese.
3. No verificar ataques de retransmisión
Una solicitud firmada válida es válida para siempre — a menos que verifiques el timestamp. Si una plataforma incluye un timestamp en su esquema de firma (Stripe lo hace; GitHub no), rechaza las solicitudes donde el timestamp es más antiguo que unos minutos. Esto previene que un atacante capture y retransmita una solicitud legítima.
4. Incluir el secreto directamente en el código fuente
Los secretos de webhook deben vivir en variables de entorno o en un gestor de secretos, nunca cometidos en control de versiones. Si se revela un secreto, un atacante puede falsificar cualquier payload para siempre — hasta que lo rotas.
Lo que HMAC no protege
HMAC demuestra que el payload fue firmado por alguien que posee el secreto. No protege contra: no — un remitente comprometido
- — si la infraestructura de firma de Stripe fuera comprometida, los eventos falsos aún tendrían firmas válidas — a menos que también valides un timestamp o un nonce
- Ataques de retransmisión — la confidencialidad
- — HMAC no cifra nada; el payload viaja en texto plano (aunque HTTPS lo maneja) Para la mayoría de las integraciones de webhooks, HMAC sobre HTTPS cubre todo lo que necesitas.
HMAC — Cómo los webhooks saben que no estás mintiendo 2
También te puede interesar
Instalar extensiones
Agregue herramientas IO a su navegador favorito para obtener acceso instantáneo y búsquedas más rápidas
恵 ¡El marcador ha llegado!
Marcador es una forma divertida de llevar un registro de tus juegos, todos los datos se almacenan en tu navegador. ¡Próximamente habrá más funciones!
Herramientas clave
Ver todo Los recién llegados
Ver todoActualizar: Nuestro última herramienta Fue agregado el 14 de junio de 2026
