PEPINO ZAPdocs
Campanhas

Disparo em massa (campanhas)

Como criar e disparar campanhas de mensagens em massa com cadência anti-ban — warmup, janela horária, ritmo com jitter, auto-pausa e validação de número — e acompanhar o resultado por destinatário via API e webhook.

Ver como Markdown

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.

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).

Ciclo de vida

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)

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.

# 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:

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):

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:

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):

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

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

# 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:

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

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.

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:

{
  "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

RecursoCampoO que faz
Warmupwarmup.{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áriawindow.{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-pausaautoPause.consecutiveThresholdPausa a campanha após N falhas/inválidos consecutivos (default 5). O motivo fica em pausedReason.
ValidaçãovalidateNumbersConfere 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}.

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).

Disparando e controlando

AçãoEndpoint
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 / cancelarPOST /v1/campaigns/{id}/complete (?cancel=true)
Editar (só DRAFT/PAUSED)PATCH /v1/campaigns/{id}
Adicionar destinatáriosPOST /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

AgregadoGET /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árioGET /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 (webhook ou WebSocket) para receber a mudança de status de cada destinatário (sent, delivered, read, responded, failed, invalid) assim que acontece.

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

Próximos passos

On this page