# Disparo em massa (campanhas) (/docs/campanhas)





Uma **campanha** envia uma mensagem-template para muitos contatos respeitando uma **cadência anti-ban**: a plataforma controla *quando* e *em que ritmo* cada mensagem sai, em vez de despejar tudo de uma vez. Você cria a campanha, configura a cadência, dispara, e acompanha o status de cada destinatário.

<Callout type="warn">
  Disparo em massa pode levar ao **banimento** do número pela Meta. A cadência reduz o risco, mas
  não o elimina: use listas com **opt-in**, conteúdo não-spam e volume moderado. O risco é do
  titular da conta. Criar/editar exige o add-on **Disparo em massa**; disparar/retomar exige também
  assinatura ativa (no modo teste é liberado para validar a integração).
</Callout>

## Ciclo de vida [#ciclo-de-vida]

```text
DRAFT ──start──> RUNNING ──pause──> PAUSED ──resume──> RUNNING ──> COMPLETED
                    │                  ▲
                    └── auto-pausa ────┘  (N falhas/inválidos consecutivos)
```

* **DRAFT** — criada, nada foi enviado. Edite e adicione/remova destinatários à vontade.
* **RUNNING** — a engine dispara em background, respeitando a cadência.
* **PAUSED** — pausada (manualmente ou por auto-pausa). Retome quando quiser.
* **COMPLETED** / **CANCELLED** — encerrada; o histórico permanece.

## Tutorial rápido (curl) [#tutorial-rápido-curl]

Fluxo completo de ponta a ponta — criar, (opcional) assinar os eventos, disparar e acompanhar. Comece pelo **sandbox** (liberado para validar a integração); em produção, troque o host e o prefixo da chave.

```bash
# Ambiente. Sandbox para testar; produção quando estiver pronto.
BASE="https://sandbox.pepinozap.com.br"   # produção: https://api.pepinozap.com.br
TOKEN="pzk_test_xxx"                       # produção: pzk_live_xxx
AUTH="Authorization: Bearer $TOKEN"
```

**1. Descubra a instância (número conectado) que vai disparar:**

```bash
curl -s "$BASE/v1/instances" -H "$AUTH"
# pegue o "id" de uma instância com status CONNECTED -> INSTANCE
INSTANCE="cm5x8a9k20001abc123def456"
```

**2. (Opcional, recomendado) Assine os eventos em tempo real.** Cadastre um webhook que recebe `campaign.status` por destinatário (guarde o `secret` para validar a assinatura):

```bash
curl -s "$BASE/v1/webhooks" -H "$AUTH" -H 'Content-Type: application/json' -d '{
  "name": "campanhas",
  "url": "https://seusistema.com/webhooks/pepino",
  "events": ["campaign.status"]
}'
```

**3. Crie a campanha** (volta `DRAFT`). Capture o `id` retornado:

```bash
curl -s "$BASE/v1/campaigns" -H "$AUTH" -H 'Content-Type: application/json' -d "{
  \"name\": \"Promo Junho\",
  \"instanceId\": \"$INSTANCE\",
  \"template\": \"Olá {{nome}}! Aproveite {{desconto}} só hoje.\",
  \"recipients\": [
    { \"phone\": \"+55 11 99999-0001\", \"name\": \"Maria\", \"vars\": { \"desconto\": \"20% off\" } },
    \"5511999990002\"
  ],
  \"cadence\": {
    \"warmup\":   { \"base\": 20, \"factor\": 1.2, \"cap\": 200 },
    \"window\":   { \"start\": \"08:00\", \"end\": \"23:00\", \"tz\": \"America/Sao_Paulo\" },
    \"pacing\":   { \"baseSeconds\": 60, \"jitterPct\": 0.2 },
    \"autoPause\":{ \"consecutiveThreshold\": 5 },
    \"validateNumbers\": true
  }
}"
# -> { "id": "camp_...", "totalRecipients": 2 }
CAMPAIGN="camp_..."
```

**4. Dispare.** A engine assume em background (responde `202` na hora):

```bash
curl -s -X POST "$BASE/v1/campaigns/$CAMPAIGN/start" -H "$AUTH"
# -> { "started": true }
```

**5. Acompanhe.** Pelos eventos `campaign.status` (passo 2) ou por polling:

```bash
# Agregado (totais por status + runtime: dia, teto de hoje, próximo envio)
curl -s "$BASE/v1/campaigns/$CAMPAIGN" -H "$AUTH"

# Por destinatário (paginado; filtre por status)
curl -s "$BASE/v1/campaigns/$CAMPAIGN/recipients?status=FAILED&page=1" -H "$AUTH"
```

**6. Controle quando precisar:**

```bash
curl -s -X POST "$BASE/v1/campaigns/$CAMPAIGN/pause"  -H "$AUTH"   # pausar
curl -s -X POST "$BASE/v1/campaigns/$CAMPAIGN/resume" -H "$AUTH"   # retomar
curl -s -X POST "$BASE/v1/campaigns/$CAMPAIGN/complete" -H "$AUTH" # encerrar
```

<Callout type="info">
  No **sandbox** o recurso é liberado para validar a integração (com um teto diário por projeto). Em
  **produção**, criar/editar exige o add-on “Disparo em massa” e disparar/retomar exige também
  assinatura ativa — sem isso a API responde `402`.
</Callout>

## Criando uma campanha [#criando-uma-campanha]

`POST /v1/campaigns` cria em **DRAFT**. A mensagem é um **template** com variáveis por destinatário, e cada destinatário pode ser só o número ou um objeto com `phone`, `name` e `vars`:

```json
{
  "name": "Promo Junho",
  "instanceId": "cm5x8a9k20001abc123def456",
  "template": "Olá {{nome}}! Aproveite {{desconto}} só hoje.",
  "recipients": [
    { "phone": "+55 11 99999-0001", "name": "Maria", "vars": { "desconto": "20% off" } },
    "5511999990002"
  ],
  "cadence": {
    "warmup": { "base": 20, "factor": 1.2, "cap": 200 },
    "window": { "start": "08:00", "end": "23:00", "tz": "America/Sao_Paulo" },
    "pacing": { "baseSeconds": 60, "jitterPct": 0.2 },
    "autoPause": { "consecutiveThreshold": 5 },
    "validateNumbers": true
  }
}
```

* **Variáveis** — `{{nome}}` vem do `name`; qualquer `{{chave}}` vem de `vars.chave`. Variável ausente vira string vazia.
* **Destinatários** — números em qualquer formato (mantemos só os dígitos); vazios e duplicados são descartados. Mínimo 1, máximo **10.000** por campanha.
* **`cadence` é opcional** — campos omitidos usam os defaults abaixo.

## A cadência anti-ban [#a-cadência-anti-ban]

| Recurso            | Campo                            | O que faz                                                                                                                                           |
| ------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Warmup**         | `warmup.{base,factor,cap}`       | Teto diário **crescente**: `cap_do_dia = min(cap, ceil(base * factor^dia))`. Ex.: base 20, fator 1.2, teto 200 — começa em 20/dia e cresce até 200. |
| **Janela horária** | `window.{start,end,tz}`          | Só envia no intervalo (no timezone). Fora dele, **pausa e retoma** na próxima abertura.                                                             |
| **Ritmo (pacing)** | `pacing.{baseSeconds,jitterPct}` | Intervalo entre envios = `baseSeconds` ± `jitterPct` (aleatório, sem rajada). Ex.: 60s ±20%.                                                        |
| **Auto-pausa**     | `autoPause.consecutiveThreshold` | Pausa a campanha após **N falhas/inválidos consecutivos** (default 5). O motivo fica em `pausedReason`.                                             |
| **Validação**      | `validateNumbers`                | Confere no WhatsApp **antes** de enviar; número sem conta vira `invalid` e **não consome** o teto do dia.                                           |

O **dia da campanha** (`dayIndex`) avança no virar do dia, no timezone configurado — junto reseta o contador `sentToday`. Acompanhe `sentToday`/`capToday` e o `nextSendAt` em `GET /v1/campaigns/{id}`.

<Callout type="info">
  Defaults quando você omite a `cadence`: warmup base 20 / fator 1.2 / teto 200; janela 08:00–23:00
  America/Sao\_Paulo; ritmo 60s ±20%; auto-pausa em 5; validação **ligada**. A janela não pode virar
  a meia-noite (início precisa ser antes do fim).
</Callout>

## Disparando e controlando [#disparando-e-controlando]

| Ação                                  | Endpoint                                            |
| ------------------------------------- | --------------------------------------------------- |
| Disparar (DRAFT/SCHEDULED → RUNNING)  | `POST /v1/campaigns/{id}/start`                     |
| Pausar (RUNNING → PAUSED)             | `POST /v1/campaigns/{id}/pause`                     |
| Retomar (PAUSED → RUNNING)            | `POST /v1/campaigns/{id}/resume`                    |
| Encerrar / cancelar                   | `POST /v1/campaigns/{id}/complete` (`?cancel=true`) |
| Editar (só DRAFT/PAUSED)              | `PATCH /v1/campaigns/{id}`                          |
| Adicionar destinatários               | `POST /v1/campaigns/{id}/recipients`                |
| Remover destinatário (ainda pendente) | `DELETE /v1/campaigns/{id}/recipients/{sendId}`     |

`start` responde `202` e a engine assume — o envio é assíncrono.

## Acompanhando o resultado [#acompanhando-o-resultado]

**Agregado** — `GET /v1/campaigns/{id}` traz `totals` por status (`pending`, `sent`, `delivered`, `read`, `responded`, `failed`, `invalid`) e o `runtime` (dia, teto e enviados de hoje, último e próximo envio).

**Por destinatário** — `GET /v1/campaigns/{id}/recipients` lista cada contato com seu `status`, motivo de erro e marcos de tempo (paginado, filtrável por `?status=`).

**Tempo real** — assine o evento [`campaign.status`](/docs/webhooks/events#campaignstatus) (webhook ou WebSocket) para receber a mudança de status de cada destinatário (`sent`, `delivered`, `read`, `responded`, `failed`, `invalid`) assim que acontece.

```json
{
  "event": "campaign.status",
  "instanceId": "cm5x8a9k20001abc123def456",
  "data": { "campaignId": "camp_3Hk9Zq2pVxL", "recipient": "5511999999999", "status": "delivered" }
}
```

## Próximos passos [#próximos-passos]

* [Referência da API — Campanhas](/docs/api/campanhas/createCampaign)
* [Eventos de webhook](/docs/webhooks/events) — inclusive `campaign.status`
* [Inbox e histórico](/docs/crm/inbox) — as respostas dos contatos ficam registradas
