# Avisos (webhooks)

O DoneTalk avisa o seu sistema do que acontece, com um POST assinado para um endereço seu: uma campanha começou ou terminou, uma mensagem foi entregue ou lida, um contato respondeu, um atendimento foi concluído com o desfecho.

## Cadastrar um endereço

Em **Configurações › Integrações › Webhooks de saída**, ou pela API com o escopo `webhooks:manage`:

```bash
curl -X POST https://api.donetalk.com/v1/c/{empresa}/webhooks \
  -H "Authorization: Bearer $DONETALK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://seu-sistema.com/donetalk/avisos", "events": ["campaign.completed", "campaign.recipient.replied", "conversation.closed"]}'
```

A resposta traz o `secret` uma única vez: guarde para conferir as assinaturas.

## O que chega

```http
POST /donetalk/avisos HTTP/1.1
Content-Type: application/json
User-Agent: DoneTalk-Webhooks/1.0
DoneTalk-Signature: t=1792756800,v1=5f0c…
X-DoneTalk-Event: conversation.closed
X-DoneTalk-Delivery: 01a3e4f5-a6b7-7c8d-9e0f-1a2b3c4d5e6f
```

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.closed",
  "occurredAt": "2026-10-24T12:20:41.000Z",
  "customerId": "01a0c0de-…",
  "data": { "...": "o conteúdo do evento" }
}
```

Avisos de uma chave de teste trazem `"test": true`.

## Conferir a assinatura

`DoneTalk-Signature` traz `t` (o horário do envio, em segundos) e `v1`, o HMAC SHA-256 de `"<t>.<corpo exato>"` com o seu `secret`. Recuse avisos com `t` de mais de 5 minutos atrás: assim ninguém reenvia um aviso antigo.

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

function avisoValido(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const esperado = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1));
}
```

```python
import hmac, hashlib, time

def aviso_valido(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    esperado = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado, parts["v1"])
```

O cabeçalho antigo `x-donetalk-signature` (`sha256=` + HMAC do corpo, sem horário) segue junto até 21/01/2027.

## Responder e tentativas

Responda `2xx` em até 10 segundos; faça o trabalho pesado depois. Sem `2xx`, o DoneTalk tenta de novo em 1 min, 5 min, 30 min, 2 h e 12 h. Depois de 20 falhas seguidas o endereço aparece como “Com falhas” (continua recebendo). Um mesmo aviso pode chegar mais de uma vez: use o `id` para não processar duas vezes. A ordem de chegada não é garantida: use `occurredAt`.

## Eventos

| Evento | Quando chega |
|---|---|
| `conversation.opened` | Um atendimento começou (o contato escreveu ou clicou num anúncio). |
| `conversation.closed` | Um atendimento foi concluído, com o desfecho, quem concluiu (pessoa, assistente de IA ou regra automática), a anotação de encerramento, o contato, a equipe, as etiquetas, quanto durou e a campanha de origem. |
| `conversation.assigned` | Um atendimento foi atribuído a uma pessoa. |
| `conversation.transferred` | Um atendimento passou para outra equipe ou pessoa. |
| `conversation.reopened` | Um atendimento concluído foi reaberto. |
| `contact.updated` | Os dados de um contato mudaram. |
| `contact.consent_changed` | O contato aceitou ou recusou receber mensagens de uma finalidade (marketing, por exemplo). |
| `message.received` | O contato mandou uma mensagem. |
| `message.sent` | Uma mensagem nossa saiu para o WhatsApp. |
| `message.delivered` | Uma mensagem chegou ao celular do contato. |
| `message.read` | O contato leu uma mensagem. |
| `message.failed` | Uma mensagem não pôde ser entregue; `error.code` é o código da Meta. |
| `flow.event` | Um fluxo chegou a um bloco “Registrar evento”, com o nome e os dados que o bloco define. |
| `appointment.booked` | Um agendamento foi marcado. |
| `appointment.rescheduled` | Um agendamento mudou de horário. |
| `appointment.cancelled` | Um agendamento foi cancelado. |
| `appointment.confirmed` | O contato confirmou o agendamento. |
| `appointment.attended` | O contato compareceu. |
| `appointment.no_show` | O contato faltou. |
| `campaign.started` | A campanha começou a enviar; a lista foi fechada. |
| `campaign.paused` | A campanha pausou: por uma pessoa, pela API ou por uma regra (modelo pausado, qualidade baixa, muitos pedidos para sair, orçamento). |
| `campaign.resumed` | A campanha voltou a enviar. |
| `campaign.completed` | A campanha terminou de enviar, com os totais. |
| `campaign.cancelled` | A campanha foi cancelada. |
| `campaign.recipient.sent` | A mensagem da campanha saiu para um contato. |
| `campaign.recipient.delivered` | A mensagem da campanha chegou ao contato. |
| `campaign.recipient.read` | O contato leu a mensagem da campanha. |
| `campaign.recipient.replied` | O contato respondeu à campanha (a primeira resposta), por texto ou tocando num botão. |
| `campaign.recipient.failed` | A mensagem da campanha falhou para um contato; `errorCode` é o código da Meta. |
| `campaign.recipient.opted_out` | O contato pediu para não receber mais campanhas (botão “Parar promoções” ou recusa na Meta). |

## Exemplos de cada evento

### conversation.opened

Um atendimento começou (o contato escreveu ou clicou num anúncio).

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.opened",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "handler": "flow",
    "source": "inbound"
  }
}
```

### conversation.closed

Um atendimento foi concluído, com o desfecho, quem concluiu (pessoa, assistente de IA ou regra automática), a anotação de encerramento, o contato, a equipe, as etiquetas, quanto durou e a campanha de origem.

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.closed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "teamId": "01a0cb22-89c0-7e02-b96b-3f4731dde701",
    "assigneeId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "outcome": "converted",
    "isSuccess": true,
    "closedBy": {
      "kind": "member",
      "id": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
      "name": "Tayomara Batista"
    },
    "actorKind": "member",
    "openedAt": "2026-10-24T12:00:04.000Z",
    "firstResponseAt": "2026-10-24T12:05:10.000Z",
    "messagesIn": 6,
    "messagesOut": 7,
    "outcomeLabel": {
      "pt-BR": "Venda",
      "en": "Sale",
      "es": "Venta"
    },
    "outcomeCategory": "success",
    "note": "Fechou o plano anual com o cupom ANA10.",
    "contact": {
      "name": "Ana Souza",
      "phone": "5548999990001"
    },
    "team": {
      "id": "01a0cb22-89c0-7e02-b96b-3f4731dde701",
      "name": "Vendas"
    },
    "tags": [
      {
        "id": "01a12150-3610-7c00-b680-188c2b4c7313",
        "name": {
          "pt-BR": "VIP",
          "en": "VIP",
          "es": "VIP"
        }
      }
    ],
    "durationSeconds": 1237,
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b"
  }
}
```

### conversation.assigned

Um atendimento foi atribuído a uma pessoa.

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.assigned",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "assigneeId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "teamId": "01a0cb22-89c0-7e02-b96b-3f4731dde701",
    "reason": "distribution",
    "previousAssigneeId": null,
    "actorKind": "system"
  }
}
```

### conversation.transferred

Um atendimento passou para outra equipe ou pessoa.

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.transferred",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "teamId": "01a0cb22-89c0-7e02-b96b-3f4731dde701",
    "assigneeId": null,
    "previousTeamId": null,
    "previousAssigneeId": null,
    "previousHandler": "flow",
    "actorKind": "flow",
    "reason": "Gestor de conta: Tayomara Batista"
  }
}
```

### conversation.reopened

Um atendimento concluído foi reaberto.

```json
{
  "id": "01a3e4f5-…",
  "type": "conversation.reopened",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677"
  }
}
```

### contact.updated

Os dados de um contato mudaram.

```json
{
  "id": "01a3e4f5-…",
  "type": "contact.updated",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "changes": [
      "fields",
      "displayName"
    ]
  }
}
```

### contact.consent_changed

O contato aceitou ou recusou receber mensagens de uma finalidade (marketing, por exemplo).

```json
{
  "id": "01a3e4f5-…",
  "type": "contact.consent_changed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "channel": "whatsapp",
    "purpose": "marketing",
    "status": "opted_out",
    "source": "keyword",
    "occurredAt": "2026-10-24T12:06:00.000Z"
  }
}
```

### message.received

O contato mandou uma mensagem.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.received",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "type": "text"
  }
}
```

### message.sent

Uma mensagem nossa saiu para o WhatsApp.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.sent",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "providerMessageId": "wamid.HBgMNTU0ODk5OTk5MDAwMRUCABEYEjE="
  }
}
```

### message.delivered

Uma mensagem chegou ao celular do contato.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.delivered",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "delivered",
    "occurredAt": "2026-10-24T12:00:06.000Z"
  }
}
```

### message.read

O contato leu uma mensagem.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.read",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "read",
    "occurredAt": "2026-10-24T12:03:51.000Z"
  }
}
```

### message.failed

Uma mensagem não pôde ser entregue; `error.code` é o código da Meta.

```json
{
  "id": "01a3e4f5-…",
  "type": "message.failed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "failed",
    "occurredAt": "2026-10-24T12:00:06.000Z",
    "error": {
      "code": "131026",
      "detail": "Message undeliverable"
    }
  }
}
```

### flow.event

Um fluxo chegou a um bloco “Registrar evento”, com o nome e os dados que o bloco define.

```json
{
  "id": "01a3e4f5-…",
  "type": "flow.event",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "name": "lead.qualified",
    "data": {
      "plano": "anual",
      "origem": "site"
    }
  }
}
```

### appointment.booked

Um agendamento foi marcado.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.booked",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "booked",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.rescheduled

Um agendamento mudou de horário.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.rescheduled",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "booked",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 1,
    "previousStartsAt": "2026-10-27T12:00:00.000Z",
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.cancelled

Um agendamento foi cancelado.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.cancelled",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "cancelled",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": "2026-10-26T18:00:00.000Z",
    "cancelledByKind": "contact",
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.confirmed

O contato confirmou o agendamento.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.confirmed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "confirmed",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": "2026-10-26T14:00:00.000Z",
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.attended

O contato compareceu.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.attended",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "attended",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### appointment.no_show

O contato faltou.

```json
{
  "id": "01a3e4f5-…",
  "type": "appointment.no_show",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "id": "01a2f00d-1234-7abc-8def-0123456789ab",
    "agendaId": "01a1228c-c584-73f7-b7e7-bfaa0d02fed0",
    "agendaName": "Visitas aos imóveis",
    "typeId": "01a1228c-c58f-796a-b3aa-54a953711c71",
    "typeName": "Visita ao imóvel",
    "typeColor": "#1daa61",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "contactName": "Ana Souza",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "channelId": "01a0cccc-0000-7000-8000-000000000002",
    "memberId": "01a0cac1-4a6f-7646-b5f8-c1d19090016a",
    "memberName": "Tayomara Batista",
    "holderIds": [
      "01a0cac1-4a6f-7646-b5f8-c1d19090016a"
    ],
    "status": "no_show",
    "startsAt": "2026-10-27T13:00:00.000Z",
    "endsAt": "2026-10-27T14:00:00.000Z",
    "timezone": "America/Sao_Paulo",
    "location": "Rua Bahia, 120 – Higienópolis, São Paulo",
    "locationKind": "address",
    "onlineUrl": null,
    "answers": {
      "imovel": "AP-2231"
    },
    "notes": null,
    "source": "api",
    "createdByKind": "api_key",
    "createdById": null,
    "confirmedAt": null,
    "cancelledAt": null,
    "cancelledByKind": null,
    "cancelReason": null,
    "rescheduleCount": 0,
    "previousStartsAt": null,
    "publicToken": "r3WqZ8",
    "createdAt": "2026-10-23T13:02:11.000Z"
  }
}
```

### campaign.started

A campanha começou a enviar; a lista foi fechada.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.started",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "running",
    "audience": 1240,
    "skipped": 24,
    "startedAt": "2026-10-24T12:00:00.000Z"
  }
}
```

### campaign.paused

A campanha pausou: por uma pessoa, pela API ou por uma regra (modelo pausado, qualidade baixa, muitos pedidos para sair, orçamento).

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.paused",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "paused",
    "reason": "optout_rate",
    "by": "system"
  }
}
```

### campaign.resumed

A campanha voltou a enviar.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.resumed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "running",
    "by": "api_key"
  }
}
```

### campaign.completed

A campanha terminou de enviar, com os totais.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.completed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "completed",
    "totals": {
      "audience": 1216,
      "sent": 1216,
      "delivered": 1198,
      "read": 874,
      "replied": 141,
      "failed": 18,
      "skipped": 24,
      "optedOut": 6
    }
  }
}
```

### campaign.cancelled

A campanha foi cancelada.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.cancelled",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "status": "cancelled",
    "by": "member"
  }
}
```

### campaign.recipient.sent

A mensagem da campanha saiu para um contato.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.sent",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "phone": "5548999990001",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "at": "2026-10-24T12:00:04.000Z"
  }
}
```

### campaign.recipient.delivered

A mensagem da campanha chegou ao contato.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.delivered",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "phone": "5548999990001",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "at": "2026-10-24T12:00:06.000Z"
  }
}
```

### campaign.recipient.read

O contato leu a mensagem da campanha.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.read",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "phone": "5548999990001",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "at": "2026-10-24T12:03:51.000Z"
  }
}
```

### campaign.recipient.replied

O contato respondeu à campanha (a primeira resposta), por texto ou tocando num botão.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.replied",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "buttonIndex": 0,
    "text": "Quero o cupom"
  }
}
```

### campaign.recipient.failed

A mensagem da campanha falhou para um contato; `errorCode` é o código da Meta.

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.failed",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "phone": "5548999990001",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "messageId": "01a3c0de-8b22-7c33-9d44-0e55f6677889",
    "errorCode": "131026"
  }
}
```

### campaign.recipient.opted_out

O contato pediu para não receber mais campanhas (botão “Parar promoções” ou recusa na Meta).

```json
{
  "id": "01a3e4f5-…",
  "type": "campaign.recipient.opted_out",
  "occurredAt": "2026-10-24T12:04:02.000Z",
  "customerId": "01a0c0de-…",
  "data": {
    "campaignId": "01a2b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
    "contactId": "01a0d05a-3ee3-75bd-9db5-dc9856f079c8",
    "conversationId": "01a3c0de-7a11-7b22-8c33-9d44e55f6677",
    "via": "button"
  }
}
```
