Anúncios incomodam? Ir Sem anúncios Hoje

HMAC Como os Webhooks Sabem que Você Não Está Mentindo

Publicado em

A cada vez que o GitHub, o Stripe ou o Shopify envia um webhook para o seu servidor, assinam o payload com HMAC. Aqui está como isso funciona exatamente — e como verificá-lo no seu próprio código.

Cada vez que o GitHub, o Stripe ou o Shopify dispara um webhook para o seu servidor, eles assinam o payload com HMAC. Aqui está exatamente como isso funciona — e como verificá-lo no seu próprio código.
ANUNCIADO Remover?

Você recebe um pedido POST alegando ser do Stripe. O payload afirma que um pagamento foi bem-sucedido. Você processa o pedido, envia os produtos — e três dias depois descobre que o pedido foi falso. Ufa.

Por isso, todos os provedores sérios de webhooks assinam seus payloads usando HMAC. Se você entende HMAC, entende por que o GitHub, o Stripe, o Shopify, o Twilio e praticamente todas as APIs modernas o usam — e saberá exatamente como verificar essas assinaturas no seu próprio código.

O que é HMAC na verdade

HMAC significa Código de Autenticação de Mensagem Baseado em Hash. Ele responde a uma pergunta: "O remetente que me enviou esta mensagem sabia o segredo compartilhado?"

Funciona misturando um hash criptográfico — geralmente SHA-256 — com uma chave secreta e o corpo da mensagem. O resultado é uma string de comprimento fixo que:

  • muda completamente se um único byte da mensagem mudar
  • não pode ser produzido sem conhecer a chave secreta
  • não pode ser revertido para revelar a chave ou a mensagem original

A fórmula é compacta: HMAC(key, message) = H((key ⊕ opad) || H((key ⊕ ipad) || message)). Você não precisa memorizar os detalhes internos — todas as linguagens têm uma implementação padrão no pacote padrão — mas é útil saber o propósito: a chave é misturada no hash duas vezes, de maneiras diferentes, para impedir um tipo de ataque chamado de ataques de extensão de comprimento.

Como um provedor de webhook o usa

Quando você registra um ponto de entrada de webhook, o provedor fornece um segredo de assinatura — uma string aleatória que apenas você e eles conhecem. Quando um evento é disparado:

  1. O provedor serializa o payload do evento para JSON (ou uma string canônica).
  2. Ele calcula HMAC-SHA256(secret, payload).
  3. Ele envia o pedido com a assinatura no cabeçalho — X-Hub-Signature-256 para o GitHub, Stripe-Signature para o Stripe e assim por diante.

No seu lado, você faz o mesmo cálculo sobre o corpo original do pedido e o compara. Se coincidirem, o payload é autêntico. Se não, descarte-o.

Verificação no código

Aqui está como a verificação parece nas linguagens mais comuns. O padrão é idêntico em todas: calcular, comparar com uma função de comparação de tempo constante.

Node.js

const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)          // rawBody must be a Buffer or string — NOT parsed JSON
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Pitão

import hmac
import hashlib

def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, signature)

PHP

function verifyWebhook(string $rawBody, string $signature, string $secret): bool {
    $expected = hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signature);
}

O detalhe crítico: usar o corpo original

O erro mais comum na implementação é passar um payload parseado e re-serializado para a função HMAC em vez dos bytes brutos originais. A ordem das chaves, espaços em branco e escape de Unicode afetam o hash. O provedor assinou os bytes exatos que enviou pela rede — você deve hash exatamente esses mesmos bytes.

No Express (Node.js), isso significa configurar o parser de corpo para preservar o buffer bruto:

app.use('/webhooks', express.raw({ type: 'application/json' }));

No Django, use request.body em vez de request.data. No Flask, use request.get_data().

Por que não usar apenas um hash simples?

Um hash simples SHA-256 do payload prova nada — qualquer pessoa pode calcular SHA256(payload) sem um segredo. O segredo da chave é o que torna o HMAC um autenticação código, não apenas um checksum. Ele responde a "quem enviou isso" em vez de apenas "o conteúdo foi corrompido durante a transmissão".

Por que não usar assinaturas assimétricas (como RSA)?

O RSA e o ECDSA permitem que o receptor verifique a assinatura sem conhecer a chave privada — isso é valioso para divulgação pública (como assinatura de código). Para webhooks, há apenas dois lados que precisam verificar a assinatura: você e o provedor. Um segredo compartilhado é mais simples, mais rápido e igualmente seguro nesse modelo. Alguns provedores (Svix, Clerk) oferecem assinatura assimétrica de webhook para casos em que você não consegue armazenar segredos no servidor.

Ataques de replay — e como pará-los

Uma assinatura HMAC válida prova autenticidade, mas não frescor. Um atacante que captura um pedido legítimo assinado pode retransmiti-lo mais tarde. O Stripe combate isso incluindo um timestamp no cabeçalho e hashando o timestamp junto com o corpo do payload. No seu lado, você rejeita qualquer pedido onde o timestamp seja mais de cinco minutos antigo. Stripe-Signature Se você estiver construindo seu próprio sistema de webhook, faça o mesmo: inclua um nonce crescente ou um timestamp Unix no mensagem assinada e rejeite pedidos expirados no servidor.

A comparação de tempo seguro não é opcional

Nunca compare assinaturas HMAC com uma verificação simples (

). Uma comparação de string curta revela informações sobre quantos bytes iniciais coincidem — um atacante que faz milhares de requisições pode reconstruir a assinatura esperada byte a byte. Sempre use uma comparação de tempo constante:===, ==PHP:

  • Node.js: crypto.timingSafeEqual()
  • Python: hmac.compare_digest()
  • Go: hash_equals()
  • Ruby: hmac.Equal()
  • Montando tudo: um manipulador de webhook pronto para produção ActiveSupport::SecurityUtils.secure_compare()

Aqui está um exemplo completo usando o cabeçalho

do GitHub no Node.js: X-Hub-Signature-256 Referência rápida: quem usa o quê

const express = require('express');
const crypto = require('crypto');

const app = express();
const WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET;

// Keep the body as raw bytes — critical!
app.use('/github/webhook', express.raw({ type: 'application/json' }));

app.post('/github/webhook', (req, res) => {
  const sigHeader = req.headers['x-hub-signature-256'];
  if (!sigHeader) return res.status(401).send('Missing signature');

  const sig = sigHeader.replace('sha256=', '');
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  // Safe to process the event now
  console.log('Event type:', req.headers['x-github-event']);

  res.sendStatus(200);
});

app.listen(3000);

Provedor

Timestamp na assinatura?CabeçalhoAlgoritmoX-Hub-Signature-256
GitHubStripe-SignatureHMAC-SHA256Não
StripeX-Shopify-Hmac-Sha256HMAC-SHA256Sim
ShopifyX-Twilio-SignatureHMAC-SHA256Não
TwilioX-Slack-SignatureHMAC-SHA1Não
FolgaPaddleHMAC-SHA256Sim
Paddle-SignatureHMAC: Como os Webhooks Sabem que Você Não Está Mentindo 2HMAC-SHA256Sim
Quer eliminar anúncios? Fique sem anúncios hoje mesmo

Instale nossas extensões

Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida

Ao Extensão do Chrome Ao Extensão de Borda Ao Extensão Firefox Ao Extensão Opera

O placar chegou!

Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!

ANUNCIADO Remover?
ANUNCIADO Remover?
ANUNCIADO Remover?

Notícias com destaques técnicos

Envolver-se

Ajude-nos a continuar fornecendo ferramentas gratuitas valiosas

Compre-me um café
ANUNCIADO Remover?