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

# Guia de Webhooks

> Como a Cakto entrega eventos, como validar a origem e como tratar retentativas.

## Como funciona

Quando algo acontece na sua conta — uma compra aprovada, um Pix gerado, uma assinatura cancelada — a Cakto envia uma requisição `POST` para a URL que você cadastrou.

```mermaid theme={null}
sequenceDiagram
    participant C as Cakto
    participant V as Sua aplicação
    C->>V: POST { secret, event, data }
    V-->>C: 2xx (recebido)
    Note over C,V: Se não vier 2xx, a Cakto reenvia
```

Você cadastra webhooks em [Criar Webhook](/api-reference/webhooks/create) ou pelo [Painel Cakto](https://app.cakto.com.br/dashboard/apps).

## Formato da entrega

| Item           | Valor              |
| -------------- | ------------------ |
| Método         | `POST`             |
| `Content-Type` | `application/json` |
| `User-Agent`   | `CaktoBot/1.0`     |

O corpo tem sempre três campos:

```json theme={null}
{
  "secret": "b3f1a9c2-7b4d-4a8e-9f01-2c6d5b8a4e37",
  "event": "purchase_approved",
  "data": {
    "id": "87956abe-940e-4e8b-8a27-82c482920f64",
    "refId": "9vbgfmg",
    "status": "waiting_payment",
    "baseAmount": 100.0,
    "checkoutUrl": "https://pay.cakto.com.br/EXAMPLE",
    "offer_type": "main",
    "customer": {
      "name": "John Doe",
      "email": "john.doe@example.com",
      "docNumber": "12345678909",
      "docType": "cpf"
    },
    "product": { "id": "ff3fdf61-...", "short_id": "AckhQ75", "name": "Produto Teste" },
    "offer": { "id": "B8BcHrY", "name": "Special Offer", "price": 10 },
    "subscription": null
  }
}
```

<Tip>
  Para ver o payload completo com dados reais da sua conta, use [Testar Webhook](/api-reference/webhooks/test-webhook). É a forma mais rápida de conhecer o formato sem esperar uma venda.
</Tip>

## Validando a origem

<Warning>
  A Cakto **não** assina o payload com HMAC nem envia header de assinatura. A validação é feita pelo campo `secret` **dentro do corpo** da requisição.
</Warning>

O `secret` é gerado automaticamente quando você cria o webhook. Sua aplicação deve comparar o `secret` recebido com o que você armazenou e **rejeitar** a requisição se não bater.

<CodeGroup>
  ```python Python icon=python theme={null}
  import hmac
  import os

  CAKTO_WEBHOOK_SECRET = os.environ["CAKTO_WEBHOOK_SECRET"]

  def handle_webhook(payload: dict):
      received = payload.get("secret", "")

      # compare_digest evita vazar informação por tempo de comparação
      if not hmac.compare_digest(received, CAKTO_WEBHOOK_SECRET):
          return 401, "unauthorized"

      event = payload["event"]
      data = payload["data"]
      # ... processe o evento
      return 200, "ok"
  ```

  ```javascript JavaScript icon=square-js theme={null}
  import crypto from 'node:crypto';

  const SECRET = process.env.CAKTO_WEBHOOK_SECRET;

  function isFromCakto(received = '') {
    const a = Buffer.from(received);
    const b = Buffer.from(SECRET);
    // timingSafeEqual exige buffers do mesmo tamanho
    return a.length === b.length && crypto.timingSafeEqual(a, b);
  }

  app.post('/webhooks/cakto', (req, res) => {
    if (!isFromCakto(req.body.secret)) {
      return res.status(401).send('unauthorized');
    }

    const { event, data } = req.body;
    // ... processe o evento
    res.sendStatus(200);
  });
  ```
</CodeGroup>

<Note>
  Como o `secret` trafega no corpo, sua URL de webhook **precisa** usar HTTPS. Trate o valor como credencial: guarde em variável de ambiente e nunca versione no git.
</Note>

## Retentativas

A entrega é considerada bem-sucedida quando sua aplicação responde com status `2xx`. Qualquer outra resposta — ou um timeout — marca a entrega como falha e agenda um reenvio.

São até **5 retentativas**, com intervalos crescentes a partir do envio original:

| Tentativa      | Intervalo após a anterior |
| -------------- | ------------------------- |
| 1ª retentativa | 5 segundos                |
| 2ª retentativa | 1 minuto                  |
| 3ª retentativa | 2min 30s                  |
| 4ª retentativa | 6 minutos                 |
| 5ª retentativa | 30 minutos                |

Depois disso a entrega é encerrada como falha, e você pode reenviá-la manualmente com [Reenviar Evento](/api-reference/webhooks/resend-event).

<Warning>
  Responda `2xx` assim que receber o evento e faça o processamento pesado de forma assíncrona. O tempo limite de resposta é de **8 segundos** — passando disso, a Cakto considera timeout e reenvia, mesmo que sua aplicação tenha processado o evento com sucesso.
</Warning>

<Tip>
  **Seu handler precisa ser idempotente.** Por causa das retentativas, o mesmo evento pode chegar mais de uma vez. Use o `data.id` do pedido como chave de deduplicação e ignore o que já processou.
</Tip>

## Catálogo de eventos

### Compra

| Evento                 | Quando dispara                          |
| ---------------------- | --------------------------------------- |
| `initiate_checkout`    | O comprador iniciou o checkout          |
| `checkout_abandonment` | O checkout foi abandonado sem pagamento |
| `purchase_approved`    | Pagamento aprovado                      |
| `purchase_refused`     | Pagamento recusado                      |
| `refund`               | Compra reembolsada                      |
| `chargeback`           | Chargeback registrado                   |

### Cobrança gerada

| Evento                      | Quando dispara                        |
| --------------------------- | ------------------------------------- |
| `pix_gerado`                | Pix gerado, aguardando pagamento      |
| `boleto_gerado`             | Boleto gerado, aguardando pagamento   |
| `picpay_gerado`             | Cobrança PicPay gerada                |
| `openfinance_nubank_gerado` | Cobrança Open Finance (Nubank) gerada |

### Assinatura

| Evento                         | Quando dispara                  |
| ------------------------------ | ------------------------------- |
| `subscription_created`         | Assinatura criada               |
| `subscription_renewed`         | Assinatura renovada com sucesso |
| `subscription_renewal_refused` | Tentativa de renovação recusada |
| `subscription_paused`          | Assinatura pausada              |
| `subscription_resumed`         | Assinatura reativada            |
| `subscription_canceled`        | Assinatura cancelada            |

<Note>
  Um mesmo webhook pode assinar vários eventos. Os eventos disponíveis para seleção estão no corpo de [Criar Webhook](/api-reference/webhooks/create).
</Note>

## Acompanhando entregas

<CardGroup cols={2}>
  <Card title="Histórico de Eventos" icon="clock-rotate-left" href="/api-reference/webhooks/event-history">
    Consulte o que foi enviado, o status e o tempo de resposta
  </Card>

  <Card title="Reenviar Evento" icon="rotate-right" href="/api-reference/webhooks/resend-event">
    Dispare de novo um evento que falhou
  </Card>

  <Card title="Testar Webhook" icon="flask" href="/api-reference/webhooks/test-webhook">
    Envie um payload de exemplo para validar sua integração
  </Card>

  <Card title="Criar Webhook" icon="plus" href="/api-reference/webhooks/create">
    Cadastre uma URL e selecione os eventos
  </Card>
</CardGroup>
