# Por que a assinatura do meu webhook não confere?

> Resolva falhas na verificação de assinatura de webhooks. Assine o corpo bruto com o segredo whsec_ completo e o valor de X-Emailit-Timestamp.

Este artigo ajuda quando o seu endpoint rejeita os webhooks do Emailit porque a assinatura que você calcula não é igual ao cabeçalho `X-Emailit-Signature`. Em quase todos os casos, os bytes que você assina ou o segredo que você usa são diferentes dos que o Emailit usou.

## Sintomas

- O seu handler retorna `401` ou `400` com “invalid signature” para todos os webhooks, incluindo os eventos de **Send test** do painel.
- As requisições na aba **Requests** do webhook mostram tentativas com falha, com o código de status de erro do seu endpoint.
- A verificação funciona em um teste unitário com um payload feito à mão, mas falha nas entregas reais.

## Causa

O Emailit assina cada requisição assim:

```text
signature = hex( HMAC-SHA256( secret, "<X-Emailit-Timestamp>.<raw request body>" ) )
```

A assinatura falha quando uma das três entradas é diferente:

- **O corpo foi serializado de novo.** Os frameworks muitas vezes interpretam o JSON antes de o seu handler rodar. Chamar `JSON.stringify(req.body)` produz espaços e uma ordem de chaves diferentes dos bytes que o Emailit enviou. O corpo é um **array** JSON de até 100 eventos, então um código que espera um objeto também pode alterar o formato dele.
- **O segredo está errado.** Use o valor completo, incluindo o prefixo `whsec_`. Cada webhook tem o próprio segredo, e redefini-lo invalida o antigo na hora, inclusive para as novas tentativas pendentes.
- **O timestamp está ausente ou é diferente.** Use o valor exato do cabeçalho `X-Emailit-Timestamp` e junte-o ao corpo com um único ponto.
- **A comparação está errada.** O cabeçalho está em hexadecimal minúsculo, não em Base64, e não tem o prefixo `sha256=`.
- **Algo na frente da sua aplicação alterou o corpo**, como um proxy que o recodifica ou o descompacta.

## Solução

1. **Leia o corpo bruto.** Capture os bytes antes que qualquer parser JSON seja executado. No Express, monte `express.raw()` apenas na rota do webhook:

```javascript title="webhook.js"
import crypto from 'node:crypto';

app.post('/webhooks/emailit', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Emailit-Timestamp');
  const expected = crypto
    .createHmac('sha256', process.env.EMAILIT_WEBHOOK_SECRET) // whsec_...
    .update(`${timestamp}.${req.body.toString('utf8')}`)
    .digest('hex');
  const received = req.get('X-Emailit-Signature') ?? '';
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
  if (!valid) return res.sendStatus(401);

  const events = JSON.parse(req.body); // an array of events
  res.sendStatus(200);
});
```

   Nos route handlers do Next.js, use `await request.text()`. No Laravel, use `$request->getContent()`. No Flask, use `request.get_data()`.

2. **Obtenha o segredo correto.** Chame [Obter um webhook](/pt/docs/api-reference/webhooks/get/), que retorna o `secret` atual. No painel, o segredo é mostrado uma única vez, então, se você o perdeu, abra o webhook, escolha **Webhook secret** para redefini-lo e atualize a sua variável de ambiente imediatamente.

3. **Envie um evento de teste.** Na página do webhook, escolha **Send test** e selecione um tipo de evento. O teste é assinado com o mesmo segredo, e a caixa de diálogo mostra o código de status e o corpo da resposta do seu endpoint.

4. **Confira a tolerância do seu relógio.** Se você rejeitar timestamps antigos para evitar ataques de replay, permita alguns minutos de diferença e mantenha o relógio do servidor sincronizado. Cada tentativa de entrega, incluindo as novas tentativas, traz um timestamp novo.

5. **Tente de novo o que falhou.** Quando a verificação funcionar, escolha **Retry failed** para reenviar as entregas com falha dos últimos 7 dias.

Para código em outras linguagens, consulte [Assinatura das requisições de webhook](/pt/docs/webhooks/request-signature/). Para o formato do corpo, consulte [Requisições de webhook](/pt/docs/webhooks/webhook-requests/).

## Ainda com problemas?

[Fale com o suporte](/contact/) ou pergunte no [Discord](https://discord.emailit.com). Informe o ID do webhook (`wh_…`) e o ID de um evento que falhou, nunca o segredo.

---
Fonte: https://emailit.com/pt/docs/kb/webhook-signature-mismatch/
