# Segurança

O seu endpoint de webhook fica exposto na internet, e qualquer um pode fazer um `POST`
nele. Sem validação, alguém poderia forjar um `contract.installment.paid` e provocar uma
baixa indevida no seu financeiro.

A Quyta assina cada entrega para que você consiga distinguir o que é legítimo.

## Como funciona a assinatura

Ao criar a assinatura de webhook no painel, você define um **segredo** — uma string que só
a Quyta e o seu sistema conhecem.

A cada entrega, a Quyta calcula o HMAC SHA-256 do corpo da requisição usando esse segredo
e envia o resultado, em hexadecimal, no header:

```
Quyta-Webhook-Signature: 6b3a1f...c91f
```

Seu endpoint recalcula o mesmo HMAC sobre o corpo recebido. Se os valores baterem, a
mensagem veio da Quyta e não foi alterada no caminho.

:::danger
O header só é enviado se a assinatura tiver um segredo configurado. **Configure um.** Sem
ele, não há como distinguir um webhook legítimo de um forjado.
:::

## Duas regras que não podem ser quebradas

**1. Assine o corpo bruto, não o JSON reserializado.**

O HMAC é calculado sobre os bytes exatos que trafegaram. Se o seu framework fizer o parse
do JSON e você reserializá-lo para calcular a assinatura, a ordem das chaves, os espaços e
o escape de caracteres podem mudar — e a validação falha sempre. Capture o corpo cru antes
de qualquer parse.

**2. Compare em tempo constante.**

Comparar strings com `==` vaza, pelo tempo de execução, quantos caracteres iniciais
coincidem — o que permite descobrir a assinatura correta byte a byte. Use a função de
comparação segura da sua linguagem.

## Implementações

### Node.js (Express)

```javascript
import crypto from "node:crypto";
import express from "express";

const app = express();
const SEGREDO = process.env.QUYTA_WEBHOOK_SECRET;

function assinaturaValida(corpoBruto, assinaturaRecebida) {
  if (!assinaturaRecebida) return false;

  const esperada = crypto
    .createHmac("sha256", SEGREDO)
    .update(corpoBruto)
    .digest("hex");

  const a = Buffer.from(esperada, "utf8");
  const b = Buffer.from(assinaturaRecebida, "utf8");

  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post(
  "/webhooks/quyta",
  // express.raw preserva o corpo bruto — essencial para o HMAC.
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!assinaturaValida(req.body, req.get("Quyta-Webhook-Signature"))) {
      return res.sendStatus(401);
    }

    const evento = JSON.parse(req.body.toString("utf8"));
    res.sendStatus(200);

    fila.publicar(evento);
  },
);
```

### PHP (Laravel)

```php
use Illuminate\Http\Request;

Route::post('/webhooks/quyta', function (Request $request) {
    $assinatura = $request->header('Quyta-Webhook-Signature');
    $esperada = hash_hmac(
        'sha256',
        $request->getContent(), // corpo bruto
        config('services.quyta.webhook_secret')
    );

    if (! $assinatura || ! hash_equals($esperada, $assinatura)) {
        abort(401);
    }

    ProcessarWebhookQuyta::dispatch($request->json()->all());

    return response()->noContent(); // 204
});
```

### Python (Flask)

```python
import hashlib
import hmac
import os

from flask import Flask, request, abort

app = Flask(__name__)
SEGREDO = os.environ["QUYTA_WEBHOOK_SECRET"].encode()


@app.post("/webhooks/quyta")
def webhook_quyta():
    assinatura = request.headers.get("Quyta-Webhook-Signature", "")
    esperada = hmac.new(SEGREDO, request.get_data(), hashlib.sha256).hexdigest()

    if not hmac.compare_digest(esperada, assinatura):
        abort(401)

    fila.publicar(request.get_json())
    return "", 200
```

## Cuidados com o segredo

- Guarde-o em variável de ambiente ou cofre de segredos, nunca no código-fonte.
- Use segredos diferentes por ambiente. O segredo de homologação não deve validar nada em
  produção.
- Gere-o com aleatoriedade criptográfica e comprimento razoável — 32 bytes em hexadecimal
  é uma escolha segura.
- Ao rotacioná-lo, aceite temporariamente as duas assinaturas: valide contra o segredo
  antigo **e** o novo enquanto a troca não se completa no painel.

## Camadas adicionais

A assinatura é a defesa principal, mas somam-se a ela:

**Exija HTTPS.** A URL cadastrada deve ser `https://`, com certificado válido — sem isso,
o corpo do webhook trafega em claro.

**Filtre pelo `User-Agent`.** Todas as entregas chegam com `Quyta-Webhook-Signature` e
`User-Agent: Quyta-Webhooks`. É um filtro barato para descartar ruído no WAF, mas
**não é autenticação**: qualquer um pode forjar um header. A validação real é o HMAC.

**Use uma URL não adivinhável.** Um caminho com um componente aleatório
(`/webhooks/quyta/a7f3e1c9`) reduz o volume de varredura automatizada que chega ao seu
endpoint. Também é defesa em profundidade, não substituto da assinatura.

**Não confie em `data` para autorizar.** Um evento diz o que aconteceu; ele não é
autorização para uma operação irreversível. Antes de uma baixa contábil de valor
relevante, vale confirmar o estado com `GET /debts/{id}`.
