# Webhooks

> Receba eventos de clique, expiração, bloqueio e conversão no seu servidor, assinados com HMAC-SHA256, com reenvio automático.

Fonte: https://curtei.com/docs/api/webhooks

Endpoint: `POST https://api.curtei.com/v1/webhooks`
Escopo: `webhooks:manage`

```bash
curl -X POST https://api.curtei.com/v1/webhooks \
  -H "Authorization: Bearer $CURTEI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://meusistema.com.br/hooks/curtei", "events": ["link.clicked", "link.expired"]}'
```

A resposta traz o `secret` uma vez só. A URL precisa ser HTTPS (porta 443 ou 8443) e apontar para um IP público.

## Eventos

| Evento | Quando |
| --- | --- |
| `link.created`, `link.updated`, `link.deleted` | Mudanças no link |
| `link.blocked` | Moderação, lista de bloqueio ou denúncia |
| `link.expired` | Passou de `expires_at` ou atingiu `max_clicks` (`reason`) |
| `link.clicked` | Um por clique humano |
| `clicks.batch` | Cliques acumulados, até 500 por entrega, a cada minuto. Prefira para volume alto |
| `conversion.created` | Conversão registrada |

## Verificar a assinatura

Cada entrega traz `X-Webhook-Timestamp` e `X-Webhook-Signature: v1=<HMAC-SHA256(secret, timestamp + "." + corpo)>`. Rejeite assinaturas diferentes e timestamps com mais de 5 minutos.

Node:

```js
import crypto from "node:crypto";

export function webhookValido(rawBody, headers, secret) {
  const ts = headers["x-webhook-timestamp"];
  const esperado = "v1=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const recebido = headers["x-webhook-signature"] ?? "";
  const recente = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  return recente && recebido.length === esperado.length && crypto.timingSafeEqual(Buffer.from(recebido), Buffer.from(esperado));
}
```

PHP:

```php
$ts = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'];
$esperado = 'v1='.hash_hmac('sha256', $ts.'.'.file_get_contents('php://input'), $secret);
$valido = hash_equals($esperado, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'])
    && abs(time() - (int) $ts) <= 300;
```

## Entrega e reenvio

- Responda `2xx` em até 10 s. Redirecionamentos não são seguidos.
- Falhou? Nova tentativa em 1 min, 5 min, 30 min, 2 h, 6 h e 24 h. Depois da 7ª, a entrega fica `failed`.
- Depois de 50 falhas seguidas o webhook é desativado; `PATCH` com `"status": "active"` reativa.
- `GET /webhooks/{id}/deliveries` mostra 30 dias de histórico; `POST .../deliveries/{id}/retry` reenvia; `POST /webhooks/{id}/test` manda um `webhook.test`.
