Assinatura
O algoritmo é HmacSHA256. A chave é o segredo completo em UTF-8, inclusive o prefixo whsec_. O segredo não é decodificado como hexadecimal.
O payload assinado é a concatenação timestamp + "." + corpo cru, também em UTF-8. timestamp é o inteiro positivo do cabeçalho, em segundos Unix. O valor do cabeçalho é exatamente:
t=<timestamp>,v1=<64 hex minúsculos>
A expressão regular aceita ^t=([0-9]+),v1=([0-9a-f]{64})$. Cabeçalho vazio, maior que 512 caracteres, timestamp 0 ou hex fora desse formato é inválido. A comparação é do cabeçalho inteiro, em tempo constante (MessageDigest.isEqual), não só do trecho v1.
X-Gateway-Timestamp repete o mesmo epoch. X-Gateway-Event-Id repete o id. X-Gateway-Signature repete MuPag-Signature; o alias é temporário.
Tolerância
Quem verifica escolhe toleranceSeconds. A janela é simétrica: now ± tolerance. O emissor não grava uma tolerância de produção. Os testes unitários passam 300. Trate 300 segundos como exemplo de teste, não como constante do remetente.
Exemplo
O corpo abaixo precisa ser o bytes crus recebidos. whsec_example é um placeholder, não um segredo real.
import { createHmac, timingSafeEqual } from "node:crypto";
const secret = "whsec_example";
const toleranceSeconds = 300;
const header = request.headers["mupag-signature"] ?? "";
const match = /^t=([0-9]+),v1=([0-9a-f]{64})$/.exec(header);
if (!match || header.length > 512) {
throw new Error("invalid signature");
}
const timestamp = Number(match[1]);
const now = Math.floor(Date.now() / 1000);
if (!Number.isSafeInteger(timestamp) || timestamp <= 0 || Math.abs(now - timestamp) > toleranceSeconds) {
throw new Error("stale signature");
}
const expected =
"t=" +
timestamp +
",v1=" +
createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
const valid =
expected.length === header.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(header));
Registre id e type. Não registre o segredo nem o cabeçalho completo.
Esta página liberou seu próximo passo?
O feedback permanece no fluxo da documentação e nunca deve incluir dados de clientes ou cartões.