Verificación de Firma
Verifique las firmas de webhook para garantizar la autenticidad de las cargas útiles de webhook entrantes
Cada entrega de webhook incluye una firma criptográfica para que pueda verificar que la solicitud proviene de MailLand y no fue manipulada durante el tránsito. Verifique siempre las firmas en producción.
Cómo Funciona la Firma
MailLand firma cada carga útil de webhook usando HMAC-SHA256 con la clave secreta de su webhook. La firma se incluye en el encabezado X-MLND-Signature.
Formato de la firma:
X-MLND-Signature: t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67| Componente | Descripción |
|---|---|
t | Marca de tiempo Unix (segundos) cuando se generó la firma |
v1 | Firma hexadecimal HMAC-SHA256 (minúsculas) |
Carga útil firmada:
{timestamp}.{raw_json_body}La firma se calcula sobre la marca de tiempo concatenada con un punto y el cuerpo JSON sin procesar de la solicitud.
Ejemplo resuelto
Compruebe su implementación con estos valores exactos antes de pasar a producción. Todo lo de abajo es real: la firma es la que MailLand produce para este secreto, esta marca de tiempo y este cuerpo.
| Entrada | Valor |
|---|---|
| Secreto | whsec_docs_example_secret_do_not_use |
Marca de tiempo (t) | 1708171200 |
| Cuerpo sin procesar | {"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"} |
Cadena firmada: la marca de tiempo, un punto literal y luego el cuerpo tal cual:
1708171200.{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}HMAC-SHA256 de esa cadena, con el secreto como clave, en hexadecimal minúscula:
b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67Por lo tanto, el encabezado de esta entrega sería:
X-MLND-Signature: t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67Reprodúzcala en una sola línea:
printf '%s' '1708171200.{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}' \
| openssl dgst -sha256 -hmac 'whsec_docs_example_secret_do_not_use'La marca de tiempo de este ejemplo está en el pasado, por lo que un verificador real la rechazaría por tolerancia. Pruebe primero el HMAC con la comprobación de tiempo desactivada y vuelva a activarla después.
Encabezados Enviados
Cada entrega de webhook incluye estos encabezados:
| Encabezado | Ejemplo | Descripción |
|---|---|---|
X-MLND-Signature | t=1708171200,v1=abc123... | Marca de tiempo y firma HMAC-SHA256 |
X-MLND-Event-Id | evt_a1b2c3d4e5f6... | ID de evento único para deduplicación |
X-MLND-Event | message.delivered | Tipo de evento |
X-MLND-Event-Version | 1 | Versión del esquema de la carga útil |
X-MLND-Webhook-Id | wh_abc123def456 | ID público de su webhook |
X-MLND-Attempt | 1 | Número de intento de entrega (basado en 1) |
X-MLND-Delivery-Id | p8r2kx7m9wq4rt | Id de este intento, nuevo en cada reintento; el publicId del historial de entregas. No sirve para deduplicar: vea Versionado |
X-MLND-Delivery-Timestamp | 1708171200 | Hora de envío de este intento (segundos Unix); siempre igual a t en la firma |
Otros dos encabezados son condicionales según el evento al que te suscribiste:
| Encabezado | Ejemplo | Se envía cuando |
|---|---|---|
X-MLND-Source | inbound | Presente solo en entregas de inbound.received |
X-MLND-Inbound-Email-Id | inb_a1b2c3d4e5f6 | Presente solo en entregas de inbound.received |
X-MLND-Message-Id no se envía actualmente en las entregas de message.*. Use el campo data.message_id del payload del evento en su lugar.
Pasos de Verificación
Extraer la marca de tiempo y la firma
Analice el encabezado X-MLND-Signature. Divida por , para obtener los componentes t=... y v1=....
Preparar la carga útil firmada
Concatene la marca de tiempo, un punto (.) y el cuerpo sin procesar de la solicitud:
signed_payload = timestamp + "." + raw_bodyCalcular la firma esperada
Genere un hash HMAC-SHA256 usando su secreto de webhook como clave y la carga útil firmada como mensaje. Conviértalo a hexadecimal en minúsculas.
Comparar las firmas
Utilice una comparación de tiempo constante para verificar si la firma calculada coincide con el valor v1 del encabezado.
Validar la marca de tiempo
Verifique que la marca de tiempo esté dentro de los 5 minutos del tiempo actual para prevenir ataques de repetición.
Ejemplos de Código
#!/usr/bin/env bash
# Compute and verify an HMAC-SHA256 webhook signature using openssl
SIGNATURE_HEADER="t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67"
RAW_BODY='{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}'
SECRET="whsec_docs_example_secret_do_not_use"
TOLERANCE=300
# Parse timestamp and signature from header
TIMESTAMP=$(echo "$SIGNATURE_HEADER" | grep -oP '(?<=t=)[^,]+')
RECEIVED_SIG=$(echo "$SIGNATURE_HEADER" | grep -oP '(?<=v1=).+')
# Check timestamp tolerance
NOW=$(date +%s)
DIFF=$(( NOW - TIMESTAMP ))
if [ "${DIFF#-}" -gt "$TOLERANCE" ]; then
echo "Error: Timestamp outside tolerance window" >&2
exit 1
fi
# Compute expected signature
SIGNED_PAYLOAD="${TIMESTAMP}.${RAW_BODY}"
EXPECTED=$(printf '%s' "$SIGNED_PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
# Compare signatures
if [ "$EXPECTED" = "$RECEIVED_SIG" ]; then
echo "Signature valid"
else
echo "Signature invalid" >&2
exit 1
fiPrevención de Ataques de Repetición
La marca de tiempo en el encabezado de la firma previene los ataques de repetición. Si un atacante captura una entrega de webhook válida y la reenvía posteriormente, la verificación de la marca de tiempo la rechazará.
La tolerancia predeterminada de MailLand es de 5 minutos (300 segundos). Puede ajustar esto en su código de verificación, pero manténgalo lo suficientemente corto para prevenir ataques de repetición significativos.
Además, cada evento tiene un event_id único (en el encabezado X-MLND-Event-Id). Almacene los IDs de eventos procesados y rechace los duplicados para una protección adicional.
MailLand deduplica el envío de su lado: un evento generado dos veces solo se despacha una vez por webhook dentro de una ventana de 24 horas. Eso no es lo mismo que "entregado como máximo una vez": una entrega fallida se reintenta, y cada reintento lleva el mismo event_id y el mismo cuerpo; solo cambian X-MLND-Attempt y la marca de tiempo de la firma. Deduplique por event_id y trate las entregas repetidas como algo esperado.
Toda entrega está firmada
No existe una ruta de entrega sin firmar. Si un webhook no tiene un secreto de firma utilizable,
MailLand hace fallar la entrega y registra el fallo en lugar de enviar la carga útil sin un
encabezado X-MLND-Signature: así que una firma faltante nunca es algo que su verificador deba
tolerar. Rechace cualquier solicitud que no lleve una.
Regeneración de Secretos
Si sospecha que el secreto de su webhook ha sido comprometido:
- Llame a
POST /v1/webhooks/{id}/regenerate-secretpara obtener un nuevo secreto - Actualice el código de verificación de su servidor con el nuevo secreto
- El secreto antiguo se invalida inmediatamente
No hay período de gracia al regenerar secretos. Actualice su código de verificación antes de la próxima entrega de webhook para evitar rechazar eventos válidos.
Errores Comunes
- Analizar el cuerpo como JSON antes de verificar: la firma se calcula sobre los bytes sin procesar, no sobre un objeto JSON re-serializado. Verifique primero, luego analice.
- Usar comparación de cadenas en lugar de comparación de tiempo constante: la comparación de cadenas ordinaria es vulnerable a ataques de temporización.
- Ignorar la marca de tiempo: sin validación de la marca de tiempo, las cargas útiles capturadas pueden reproducirse indefinidamente.
- Codificar el secreto de forma fija: utilice variables de entorno o un administrador de secretos.
Próximos Pasos
- Lógica de reintentos: comprenda el comportamiento de reintento y el manejo de errores
- Eventos: referencia completa de tipos de eventos
- Descripción general de webhooks: cree y administre webhooks