# Verificar a assinatura

Toda entrega chega com o header:

```http
X-AutoConsulta-Signature: t=1756006190,v1=8f3a…c21b
```

- **`t`** — o instante do envio, em segundos (epoch)
- **`v1`** — `HMAC-SHA256` em hexadecimal

## O que é assinado

```
<t> + "." + <corpo cru>
```

O `t` do header, um ponto, e **o corpo exatamente como chegou**.

:::caution[Use o corpo CRU, nunca o objeto reserializado]
Se você fizer `JSON.parse` e depois `JSON.stringify` para conferir, os bytes mudam — espaçamento,
ordem de chaves, escape de acento — e a assinatura **nunca** vai bater, mesmo estando correta.

Muitos frameworks já consumiram o corpo quando seu código roda. Configure o seu para guardar o
corpo cru (em Express, a opção `verify` do `express.json()`).
:::

## O procedimento

```js
import crypto from 'node:crypto'

function assinaturaConfere(corpoCru, headerAssinatura, segredos) {
  const partes = Object.fromEntries(
    headerAssinatura.split(',').map((p) => p.split('=').map((s) => s.trim())),
  )
  const t = partes.t
  if (!t) return false

  // pode vir mais de um v1 (janela de rotação) — aceite se QUALQUER um casar
  const recebidas = headerAssinatura
    .split(',').map((p) => p.split('=').map((s) => s.trim()))
    .filter(([k]) => k === 'v1').map(([, v]) => v)

  const material = `${t}.${corpoCru}`

  return recebidas.some((recebida) =>
    segredos.some((segredo) => {
      const esperada = crypto.createHmac('sha256', segredo).update(material).digest('hex')
      const a = Buffer.from(recebida, 'utf8')
      const b = Buffer.from(esperada, 'utf8')
      // comparação em tempo constante: `===` vaza, pelo tempo, quantos bytes bateram
      return a.length === b.length && crypto.timingSafeEqual(a, b)
    }),
  )
}
```

**Se não conferir, responda com erro e descarte.** Uma assinatura que não bate significa que o
corpo não veio de nós, ou não veio íntegro.

## Dois detalhes que quebram integrações

**Aceite mais de um `v1`.** Durante uma rotação de segredo vão duas assinaturas no mesmo header, a
nova primeiro. Um código que leia só a primeira ocorrência funciona — mas um que valide *apenas*
contra o segredo atual derruba toda entrega da janela de 24 horas.

**Compare em tempo constante.** Não use `===`.

## Sobre repetição de mensagem (*replay*)

O `t` entra no material assinado, então ninguém pode reaproveitar um corpo assinado alterando o
horário: mudar o `t` invalida a assinatura.

:::caution[Mas nós não impomos janela nenhuma — a decisão é sua]
O servidor **não rejeita** uma entrega por causa do `t`. Se você quer proteção contra reenvio de
uma mensagem capturada, é o **seu** código que precisa comparar o `t` com o horário atual e recusar
o que estiver velho demais.

Recomendamos **5 minutos** de tolerância. É recomendação, não comportamento do servidor.

O `t` é carimbado no instante do envio, então em condições normais ele chega com poucos segundos
de idade. A folga de 5 minutos existe para atraso de rede e diferença de relógio entre servidores,
não para o caminho normal.
:::

## Verificação em ordem

1. Ler o corpo **cru**
2. Conferir a assinatura (aceitando múltiplos `v1`, em tempo constante)
3. Só então interpretar o JSON
4. Responder **2xx** rápido — e processar depois, se for demorado

O passo 4 importa: o tempo limite de cada entrega é **10 segundos**.
