Assinatura HMAC
Para garantir que os eventos são realmente da GDREdu (e não de um atacante), todas as requisições de webhook incluem uma assinatura HMAC-SHA256 no header X-GDREdu-Signature.
Como funciona
- Quando você cria um webhook, a API gera um secret aleatório (formato
whsec_<64 hex>) - A cada evento, a API calcula
HMAC-SHA256(secret, body_json)e envia no headerX-GDREdu-Signatureno formatosha256=<hex> - Seu receptor deve recalcular a mesma assinatura e comparar com a recebida
Formato do header
X-GDREdu-Signature: sha256=7d38c5b34c1f8b3a09d3c7c5e8e6f5d4c3b2a1098765432100abcdef0123456789
O prefixo sha256= é fixo e o hex após ele é o resultado do HMAC.
Onde obter o secret
O secret é gerado uma vez no momento da criação do webhook (POST /v1/webhooks) e retornado apenas nessa resposta:
{
"id": "...",
"url": "https://...",
"events": ["equipment.recognized"],
"isActive": true,
"secretPrefix": "whsec_ab12cd34",
"secret": "whsec_ab12cd34ef567890...64chars...",
...
}:::danger[Armazene o secret com segurança]
O secret completo nunca mais será exibido. Apenas o prefixo (secretPrefix) fica visível na listagem. Se você perdê-lo, crie um novo webhook e delete o antigo.
:::
Verificação em Node.js
import crypto from 'crypto'
const SECRET = 'whsec_...' // armazenado com segurança
function verifySignature (rawBody, signatureHeader, secret) {
// rawBody deve ser o body EXATO recebido (string, não objeto re-serializado)
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
// Comparação em tempo constante para evitar timing attacks
if (signatureHeader.length !== expected.length) return false
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected)
)
}Verificação em Python
import hmac
import hashlib
SECRET = b'whsec_...'
def verify_signature(raw_body: bytes, signature_header: str) -> bool:
expected = 'sha256=' + hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature_header, expected)Verificação em PHP
$secret = 'whsec_...';
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_GDREDU_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($signature, $expected)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($rawBody, true);Importante
- Use o body raw (string exata), nunca re-serialize o JSON antes de calcular o HMAC (espaços, ordenação de chaves, etc. mudam o hash).
- Use comparação em tempo constante (
crypto.timingSafeEqual,hmac.compare_digest,hash_equals) para evitar timing attacks. - Rejeite webhooks com assinatura inválida — mesmo que o payload pareça legítimo.
- Rotacione o secret periodicamente ou em caso de suspeita de comprometimento.
Updated about 21 hours ago
Did this page help you?
