# Os 21 tipos de evento com o seu payload

20 tipos **assináveis** + `webhook.test` (não assinável). Um endpoint com lista de `events` explícita recebe SÓ esses tipos; lista vazia/omitida = todos (os novos entram sozinhos). O `data` de cada um:

| Tipo | Assinável | Descrição |

| --- | --- | --- |

| `message.received` | ✓ | Um cliente escreveu para você. data.message = objeto de mensagem do WhatsApp (texto/imagem/áudio/…); data.contacts[] identifica o remetente. |

| `message.echo` | ✓ | 🌟 O diferencial do Coexistence: uma mensagem que o DONO enviou PELO CELULAR, com o seu conteúdo. origin='device' (não consome o plano). from = o seu número comercial. |

| `message.status` | ✓ | Recibo de uma mensagem que você enviou por /v1. origin (api\|device) distingue o seu envio do envio feito à mão pelo celular. Um recibo 'device' NÃO tem mensagem sua por trás — é o esperado. Desde 29/09/2026 também chega quando a Meta recusa o NOSSO envio depois do 202: status 'failed', stage 'send' (campo opcional), failureCode (o mesmo catálogo de lastErrorCode) e messageId = o seu msg_… (a Meta nunca atribuiu um wamid). Uma única vez por mensagem. |

| `connection.created` | ✓ | Um número novo foi conectado (painel ou onboarding hospedado). O seu externalId viaja no envelope → provisione o cliente final no seu sistema. |

| `connection.status_changed` | ✓ | Mudou o estado de uma conexão (ex. status='disconnected'). Alerte ou pause os envios para esse número. |

| `connection.usage_threshold_reached` | ✓ | O limite de UM número (o seu monthlyMessageLimit) chegou a 70/90/100%. É emitido UMA vez por (número, período, limiar). Na prática: avise esse cliente final ou aumente o teto dele. |

| `usage.threshold_reached` | ✓ | A cota da CONTA (soma de todos os números) atingiu um limiar. Só existe com um plano sob medida com cota pactuada: os planos públicos não têm teto e não o emitem. connection vem em null, sequence 0. |

| `history.synced` | ✓ | (Coexistence) resumo de cada lote do histórico que a Meta sincroniza ao conectar: quantas conversas e quantas mensagens, a fase e o andamento (progress 100 = a Meta terminou). shared:false = o dono não compartilhou o histórico no app. As mensagens chegam em history.batch só se foi escolhido recebê-las antes de conectar. |

| `history.batch` | ✓ | (Coexistence) as mensagens do histórico —até 6 meses, as duas partes, sem grupos—, se o dono escolheu recebê-lo antes de conectar. Até 500 por evento, no formato da Meta (message), com contact e direction. Deduplique por batchId: um lote pode chegar duas vezes. Vai por uma fila separada que não atrasa o que é ao vivo. Com o seu 2xx nós o apagamos; se não, aos 7 dias. |

| `history.completed` | ✓ | (Coexistence) não resta mais nada do histórico desse número para entregar: quantas mensagens foram entregues (por webhook ou pela API), quantas venceram e quantas não puderam ser guardadas. Chega uma vez. |

| `history.dropped` | ✓ | (Coexistence) não conseguimos guardar o histórico desse número para entregá-lo a você (reason: storage_limit, ou disabled). O que já foi guardado é entregue igual. O histórico continua no celular. Chega uma vez. |

| `template.status_changed` | ✓ | Mudou o estado de um template na Meta (aprovado, recusado, pausado, desativado, qualidade ou categoria). Traz o estado novo e o motivo da recusa traduzido. Chega por uma sincronização periódica: com alguns minutos de atraso. |

| `template.draft_submitted` | ✓ | O seu cliente final propôs um template a partir de um link de proposta. O rascunho NÃO está na Meta: só chega à Meta quando você o aprova. |

| `account.trial_warning` | ✓ | O teste grátis da conta termina em breve (data.days = 7, 3 ou 1). connection=null. |

| `account.trial_expired` | ✓ | O teste grátis terminou: o envio é bloqueado. O recebimento é cortado 30 dias depois do bloqueio, com aviso 7 dias antes. connection=null. |

| `subscription.activated` | ✓ | (Faturamento) o primeiro pagamento foi confirmado e a conta do cliente do revendedor passou a ativa. connection=null. Reaja ativando o serviço dele. |

| `subscription.cancelled` | ✓ | (Faturamento) cancelamento da assinatura. Continua funcionando até accessUntil; depois o envio é bloqueado, e o recebimento é cortado 30 dias depois do bloqueio (com aviso 7 dias antes). connection=null. |

| `billing.payment_failed` | ✓ | (Faturamento) uma cobrança recorrente falhou. Entra em aviso/carência (ainda não corta nada). Avise o seu cliente ANTES do corte. connection=null. |

| `billing.payment_recovered` | ✓ | (Faturamento) o pagamento foi regularizado e a conta foi reativada sozinha. connection=null. |

| `billing.amount_updated` | ✓ | (Faturamento) mudou o valor da sua próxima renovação. Você pode mostrar o valor ao seu cliente antes de ser cobrado. connection=null. ⚠ Com tarifa plana a mudança é do PLANO ou da cotação: `overageArs` e `overageMessages` chegam em 0 (só um plano sob medida com cota pactuada os move). |

| `webhook.test` | — | O evento do botão 'Testar' do painel. NÃO é assinável: é entregue SEMPRE a esse endpoint (mesmo que tenha lista de eventos explícita) para validar conectividade + assinatura. connection=null, data.test=true. Nunca o trate como tráfego real. |

**`message.received`** — data

```json
{
  "message": {
    "id": "wamid.HBgL…",
    "from": "5493511234567",
    "type": "text",
    "text": {
      "body": "hola"
    }
  },
  "contacts": [
    {
      "wa_id": "5493511234567",
      "profile": {
        "name": "Ana"
      }
    }
  ]
}
```

**`message.echo`** — data

```json
{
  "origin": "device",
  "message": {
    "id": "wamid.HBgL…",
    "from": "5493516516690",
    "to": "5493511234567",
    "type": "text",
    "timestamp": "1754531482",
    "text": {
      "body": "te confirmo el turno para mañana 10hs"
    }
  }
}
```

**`message.status`** — data

```json
{
  "messageId": "wamid.HBgL…",
  "status": "read",
  "origin": "device",
  "recipient": "5493511234567",
  "timestamp": "2026-08-06T00:31:25.000Z"
}
```

**`connection.created`** — data

```json
{
  "status": "connected"
}
```

**`connection.status_changed`** — data

```json
{
  "status": "disconnected",
  "previousStatus": "connected"
}
```

**`connection.usage_threshold_reached`** — data

```json
{
  "scope": "connection",
  "threshold": 90,
  "used": 900,
  "limit": 1000,
  "period": "2026-08",
  "connection": {
    "id": "conn_4eede070e5a84d1590bdce2ea1d837bc",
    "label": "Farmacia López",
    "externalId": "farmacia-lopez"
  }
}
```

**`usage.threshold_reached`** — data

```json
{
  "scope": "account",
  "threshold": 90,
  "used": 22610,
  "limit": 25000,
  "period": "2026-08"
}
```

**`history.synced`** — data

```json
{
  "conversations": 34,
  "messages": 512,
  "shared": true,
  "phase": 0,
  "chunkOrder": 1,
  "progress": 55
}
```

**`history.batch`** — data

```json
{
  "batchId": "hb_3c1f0a9b8e7d4c2b9a1f0e3d2c1b0a9f",
  "seq": 1,
  "phase": 1,
  "chunkOrder": 3,
  "progress": 40,
  "count": 2,
  "expiresAt": "2026-10-11T03:00:00.000Z",
  "messages": [
    {
      "contact": "5493511234567",
      "direction": "in",
      "message": {
        "id": "wamid.HBgL…",
        "from": "5493511234567",
        "timestamp": "1739230970",
        "type": "text",
        "text": {
          "body": "¿Tienen turno el lunes?"
        },
        "history_context": {
          "status": "READ"
        }
      }
    },
    {
      "contact": "5493511234567",
      "direction": "out",
      "message": {
        "id": "wamid.HBgM…",
        "from": "5493510000000",
        "timestamp": "1739231001",
        "type": "text",
        "text": {
          "body": "Sí, a las 10."
        },
        "history_context": {
          "status": "DELIVERED"
        }
      }
    }
  ]
}
```

**`history.completed`** — data

```json
{
  "batches": 12,
  "delivered": 5480,
  "deliveredByWebhook": 5480,
  "deliveredByApi": 0,
  "expired": 0,
  "dropped": 0
}
```

**`history.dropped`** — data

```json
{
  "reason": "storage_limit",
  "messages": 500
}
```

**`template.status_changed`** — data

```json
{
  "template": {
    "name": "turno_confirmado",
    "language": "es_AR"
  },
  "event": "message_template_status_update",
  "status": "APPROVED",
  "category": "UTILITY",
  "qualityScore": null,
  "reason": null,
  "rejection": null
}
```

**`template.draft_submitted`** — data

```json
{
  "draft": {
    "id": "tpl_…",
    "name": "promo_octubre",
    "language": "es_AR",
    "suggestedCategory": "MARKETING"
  },
  "proposedVia": {
    "linkId": "onb_…",
    "label": "Farmacia Centro"
  },
  "clientNotes": null,
  "precheck": {
    "warnings": []
  }
}
```

**`account.trial_warning`** — data

```json
{
  "days": 3,
  "trialEndsAt": "2026-09-13T00:00:00.000Z"
}
```

**`account.trial_expired`** — data

```json
{
  "trialEndsAt": "2026-09-13T00:00:00.000Z"
}
```

**`subscription.activated`** — data

```json
{
  "planCode": "growth",
  "periodEnd": "2026-09-13T00:00:00.000Z"
}
```

**`subscription.cancelled`** — data

```json
{
  "accessUntil": "2026-09-13T00:00:00.000Z"
}
```

**`billing.payment_failed`** — data

```json
{
  "paymentId": "123456789",
  "amount": 59000,
  "currency": "ARS"
}
```

**`billing.payment_recovered`** — data

```json
{
  "planCode": "growth",
  "paymentId": "123456789"
}
```

**`billing.amount_updated`** — data

```json
{
  "totalArs": 52500,
  "baseArs": 52500,
  "overageArs": 0,
  "overageMessages": 0
}
```

**`webhook.test`** — data

```json
{
  "type": "test",
  "test": true,
  "message": "WAIA Connect test event — your endpoint is reachable and verifying signatures. This is NOT real traffic."
}
```
