Cuando un webhook tiene un secreto, cada solicitud se firma con HMAC-SHA256 sobre la cabecera de marca de tiempo y el cuerpo sin procesar.
X-Mateflow-Signature = "sha256=" + hex(
HMAC-SHA256(secret, X-Mateflow-Timestamp + "." + raw_request_body)
)
- Usa los bytes sin procesar. Los caracteres especiales en HTML pueden aparecer escapados en el cuerpo, así que si analizas el JSON y lo vuelves a serializar obtienes bytes distintos y la firma no coincidirá.
- Toma la marca de tiempo de la cabecera, no del cuerpo. Un reenvío manual actualiza solo la marca de tiempo de la cabecera y la firma.
- Comprobar la marca de tiempo no interfiere con los reintentos. Cada reintento automático y cada reenvío manual se firman de nuevo, así que una ventana de 5 minutos es suficiente.
const crypto = require('crypto');
// rawBody must be the untouched bytes, e.g. from express.raw({ type: 'application/json' }).
function verifyMateflowSignature(rawBody, headers, secret, toleranceSeconds = 300) {
const timestamp = headers['x-mateflow-timestamp'];
const received = headers['x-mateflow-signature'] || '';
if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) {
return false;
}
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest('hex');
const a = Buffer.from(received);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hashlib
import hmac
import time
def verify_mateflow_signature(raw_body: bytes, headers, secret: str, tolerance_seconds: int = 300) -> bool:
timestamp = headers.get("X-Mateflow-Timestamp", "")
received = headers.get("X-Mateflow-Signature", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance_seconds:
return False
signed = timestamp.encode() + b"." + raw_body
expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(received.encode(), expected.encode())Vector de prueba
Usa estos valores para comprobar tu cálculo de la firma. La marca de tiempo ha caducado, así que amplía tu ventana de tiempo mientras pruebas.
secret my_signing_secret
X-Mateflow-Timestamp 1793664000
raw body (UTF-8) {"event":"member.joined","data":{"username":"张三"}}
X-Mateflow-Signature sha256=3f4c83828ce0086150c9bd48c1654899357b7c05265c1d1b6c172225ee04b4b4Rotar un secreto
Un secreto nuevo entra en vigor en cuanto se guarda, y los reintentos en cola también se firman con él. No hay un periodo en el que Mateflow firme con ambos, así que haz que tu receptor acepte tanto el secreto antiguo como el nuevo durante el cambio.