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

  1. Quando você cria um webhook, a API gera um secret aleatório (formato whsec_<64 hex>)
  2. A cada evento, a API calcula HMAC-SHA256(secret, body_json) e envia no header X-GDREdu-Signature no formato sha256=<hex>
  3. 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.


Did this page help you?