> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cativa.digital/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Como webhooks funcionam na Cativa: tipos de eventos, garantias de entrega, assinatura HMAC e retries.

Webhooks são a forma da Cativa **te avisar** quando algo acontece — em vez de você ficar perguntando à API a cada minuto. Esta página é a visão de alto nível. Para o passo a passo de cadastro, verificação de assinatura e retries, veja [Cadastrando e verificando webhooks](/pt-BR/webhooks/subscribing-and-verifying).

## Como funciona

Quando algo relevante acontece num tenant (usuário recebe badge, post é criado, pagamento é confirmado), a Cativa monta o payload do evento e faz `POST` no **seu endpoint público** com `Content-Type: application/json`.

```
Ação no tenant
       │
       ▼
Cativa monta o payload
       │
       ▼
POST https://seu-app.com/webhooks/cativa
```

Você cadastra a URL e os tipos de evento que quer escutar no painel da Cativa, em **Console > Webhooks**. Cada listener recebe um `secret` próprio (formato `whsec_` + 64 hex), usado para assinar todo disparo daquele listener.

## Garantias de entrega

<CardGroup cols={2}>
  <Card title="At-least-once" icon="repeat">
    Cada evento é entregue **pelo menos uma vez**. Sempre use o `X-Cativa-Execution-Id` para idempotência no seu lado, evitando processar duas vezes em caso de duplicata.
  </Card>

  <Card title="Ordem NÃO garantida" icon="shuffle">
    Eventos podem chegar fora da ordem em que aconteceram. Não escreva código que depende de receber `user_created` antes de `user_received_badge` — eles podem inverter.
  </Card>
</CardGroup>

### Por que sem ordem?

A Cativa entrega eventos em paralelo para chegar rápido ao seu endpoint. Forçar ordem reduziria o throughput em ordem de magnitude. Em vez disso, o payload de cada evento contém **todos os dados que você precisa** para processá-lo isoladamente.

## Assinatura HMAC (X-Cativa-Signature)

Todo disparo é assinado com **HMAC-SHA256** usando o `secret` do listener. Você confere a assinatura antes de processar o evento, garantindo que o request veio mesmo da Cativa e que o body não foi adulterado em trânsito.

A assinatura chega no header:

```
X-Cativa-Signature: t=1715177521,v1=8c1d4e2a3b5f4d8a9c6e7f0b1a2d3e4f8a9c6e7f0b1a2d3e4f8a9c6e7f0b1a2d
```

* `t` — timestamp Unix (em segundos) do momento do disparo.
* `v1` — assinatura HMAC-SHA256 (hex) sobre a string `"<t>.<rawBody>"`, usando o `secret` do listener como chave.

O exemplo de verificação completo (Node, Python, Go, C#) está em [Cadastrando e verificando webhooks](/pt-BR/webhooks/subscribing-and-verifying#verificando-a-assinatura-hmac).

<Warning>
  Compute o HMAC sobre o **body bruto** (a string exata recebida), não sobre o JSON re-serializado. Re-serializar muda espaços em branco e ordem de chaves, o que invalida a assinatura.
</Warning>

## Retries com backoff

Se seu endpoint não responder com `2xx`, a Cativa re-tenta de acordo com a curva:

```
30s  →  5min  →  30min  →  2h  →  6h  →  24h
```

São **6 retries** após a tentativa inicial — **7 entregas** no total, cobrindo cerca de **33 horas**. A Cativa respeita o header `Retry-After` que você retornar (até o limite máximo da próxima janela do backoff).

### Quando re-tenta vs falha permanente

| Status                          | Comportamento                    |
| ------------------------------- | -------------------------------- |
| `2xx`                           | Sucesso — sem retry              |
| `408 Request Timeout`           | Retry                            |
| `429 Too Many Requests`         | Retry (respeita `Retry-After`)   |
| `5xx` (500, 502, 503, 504, ...) | Retry                            |
| Erros de rede / timeouts TCP    | Retry                            |
| `400 Bad Request`               | **Falha permanente** — sem retry |
| `401 Unauthorized`              | **Falha permanente** — sem retry |
| `403 Forbidden`                 | **Falha permanente** — sem retry |
| `404 Not Found`                 | **Falha permanente** — sem retry |
| `410 Gone`                      | **Falha permanente** — sem retry |

Se todas as tentativas falharem, o disparo é registrado internamente como falho. A v1 ainda **não** tem painel no Console para inspecionar entregas falhas — entre em contato com o suporte da Cativa para investigar.

## Formato do payload

<Warning>
  Diferente de muitas APIs, o payload **não usa um envelope genérico** (`{id, type, data}`). Cada evento tem um shape próprio em **PascalCase**, com `CustomerId` no nível raiz para roteamento multi-tenant.
</Warning>

Exemplo (payload de `user_received_badge`):

```json theme={null}
{
  "CustomerId": "01HQ0...",
  "BadgeId": "01HQ4...",
  "BadgeName": "Premium",
  "User": {
    "Id": "01HQ7Z3X4Y5Z6A7B8C9D0E1F2G",
    "Email": "maria@exemplo.com",
    "DisplayName": "Maria Silva"
  },
  "ReceivedAt": "2026-05-08T14:32:01Z"
}
```

Os campos comuns ao envelope de qualquer evento:

| Campo              | Descrição                                                                                                                                                                 |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CustomerId`       | ID do tenant que originou o evento. **Use para rotear quando seu endpoint recebe webhooks de múltiplos tenants.**                                                         |
| Campos específicos | Cada evento adiciona seus próprios campos (ex: `BadgeId`, `BadgeName`, `User`, `ReceivedAt` para `user_received_badge`). Veja a página de cada evento para o shape exato. |

## Catálogo de eventos

A Cativa expõe eventos para as ações principais da plataforma. Comece pelo evento canônico:

<CardGroup cols={2}>
  <Card title="user_received_badge" icon="shield-check" href="/pt-BR/webhooks/events/user-received-badge">
    Disparado quando um badge é atribuído a um usuário. Página de referência completa com payload e exemplo de receiver.
  </Card>

  <Card title="user_created" icon="user-plus" href="/pt-BR/webhooks/events/user-created">
    Novo usuário cadastrado.
  </Card>

  <Card title="user_joined_group" icon="users" href="/pt-BR/webhooks/events/user-joined-group">
    Usuário entrou num grupo (manual ou via badge).
  </Card>

  <Card title="post_created" icon="message" href="/pt-BR/webhooks/events/post-created">
    Novo post publicado num grupo.
  </Card>

  <Card title="paywall_payment_completed" icon="credit-card" href="/pt-BR/webhooks/events/paywall-payment-completed">
    Pagamento do paywall concluído com sucesso.
  </Card>
</CardGroup>

A lista completa de eventos disponíveis (incluindo `comment_created`, `course_completed`, `lesson_completed` e `user_received_private_message`) está em [Cadastrando e verificando webhooks](/pt-BR/webhooks/subscribing-and-verifying#eventos-disponíveis).

## Antipattern: processar eventos síncronamente no endpoint

<Warning>
  **Não** faça operações lentas (chamadas HTTP, queries pesadas, geração de relatórios) **dentro** do handler do webhook. Volte um `2xx` o mais rápido possível e processe em background.

  O padrão correto: o endpoint **enfileira o evento na sua própria fila e responde 200**. Um worker do seu lado processa depois, com calma.

  ```js theme={null}
  app.post('/webhooks/cativa', express.json(), async (req, res) => {
    // Enfileira para processar depois — não processa aqui
    await myQueue.enqueue(req.body);
    res.status(200).send('ok');
  });
  ```
</Warning>

## Idempotência no seu lado

Como entrega é at-least-once, você precisa **detectar duplicatas**. Use o header `X-Cativa-Execution-Id` (sempre enviado e estável entre retries do mesmo evento) como chave de deduplicação:

```js theme={null}
async function processEvent(executionId, payload) {
  const alreadyProcessed = await db.events.exists(executionId);
  if (alreadyProcessed) return;

  await db.transaction(async (tx) => {
    await applyBusinessLogic(tx, payload);
    await tx.events.insert({ id: executionId, processedAt: new Date() });
  });
}
```

Salvar o identificador de execução na mesma transação da lógica de negócio garante que ou tudo aconteceu, ou nada aconteceu — sem chance de processar duas vezes.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Cadastrando e verificando webhooks" icon="webhook" href="/pt-BR/webhooks/subscribing-and-verifying">
    Como cadastrar listener, verificar HMAC, lidar com retries.
  </Card>

  <Card title="user_received_badge" icon="shield-check" href="/pt-BR/webhooks/events/user-received-badge">
    Página de referência completa de um evento — exemplo concreto de payload e receiver.
  </Card>
</CardGroup>
