# Verificação e Segurança

Todo webhook chega com o header `X-Webhook-Signature`, que não é um HMAC simples do corpo. Ele é composto:

```
X-Webhook-Signature: t=1757083200000,v1=9f2b8c...
```

A string assinada é o timestamp, um ponto, e o corpo:

```
v1 = HMAC_SHA256(secret, `${t}.${corpo_bruto}`)
```

> ⚠️ **Atenção:** Use o corpo bruto, byte a byte. Se você fizer `JSON.parse` e reserializar, o espaçamento e a ordem das chaves mudam e o HMAC deixa de bater.

Em Express, isso significa capturar o raw body antes do `express.json()`:

```javascript
import crypto from 'crypto';
import express from 'express';

const app = express();

app.post('/webhooks/validapay',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const header = req.get('X-Webhook-Signature') ?? '';
    const { t, v1 } = Object.fromEntries(
      header.split(',').map((part) => part.split('='))
    );

    const esperado = crypto
      .createHmac('sha256', process.env.VALIDAPAY_WEBHOOK_SECRET)
      .update(`${t}.${req.body.toString('utf8')}`)
      .digest('hex');

    const confere =
      v1?.length === esperado.length &&
      crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperado));

    if (!confere) return res.sendStatus(401);

    const idadeMs = Date.now() - Number(t);
    if (idadeMs > 5 * 60 * 1000) return res.sendStatus(401);

    res.sendStatus(200);
    processarDepois(JSON.parse(req.body.toString('utf8')));
  }
);
```

> ⚠️ **Atenção:** Compare com `timingSafeEqual`, não com `===`. E rejeite assinaturas antigas. Sem janela de tolerância, uma assinatura capturada vale para sempre.

## Headers

| Header | Descrição |
|---|---|
| `X-Webhook-Signature` | `t=<timestamp>,v1=<hmac hex>` |
| `x-access-token` | Token estático opcional, se configurado no cadastro |

## Próximos passos

- [Voltar para Webhooks](/webhooks.md)
- [Catálogo de eventos](/webhooks.md#eventos-disponiveis)
