Campanhas e aceite
Mande campanhas de novidades e lotes de lembretes pelo WhatsApp, acompanhe os totais e registre o aceite de novidades que a pessoa deu no seu sistema. Os envios saem pelo mesmo motor do painel, com as mesmas conferências.
campaignsPacoteIntegrações (R$ 49 por mês, por conta). Sem ele, a API responde 403 plan_not_allowed.| Rota | O que faz |
|---|---|
POST /v1/campaigns | Campanha de novidades ou lote de lembretes, com modelo aprovado. |
GET /v1/campaigns/{id} | O status e os totais. |
POST /v1/contacts/{contact}/consent | Registra o aceite (ou a retirada) de novidades no WhatsApp. |
As regras das campanhas (aceite, SAIR, 18+, horário e custo) estão em Campanhas e consentimento.
POST/v1/campaigns
kind marketing: um modelo de marketing para um público (etiquetas, contatos ou ids externos); só recebe quem aceitou novidades (e, com bebida ou remédio, quem confirmou 18 anos). kind utility_reminder: um modelo de utilidade com uma linha por envio, cada uma com o contato, a hora e as variáveis dela. Quem pediu para sair é pulado; o envio confere tudo de novo, como no painel. Idempotency-Key obrigatória: repetir não cria outra campanha.
curl -X POST "https://boavoz.com/api/v1/campaigns" \
-H "Authorization: Bearer $BOAVOZ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3b9f2c8e-5a1d-4e7b-9c6f-2d8a4e1b7c90" \
-d '{
"bot_id": "bot_7c9e6679-7425-40de-944b-e07fc1f90ae7",
"kind": "marketing",
"name": "Teste Postman",
"template": {
"name": "promo_sexta",
"language": "pt_BR",
"variables": [
{
"type": "first_name",
"fallback": "cliente"
}
]
},
"audience": {
"tags": [
"vip"
]
}
}'{
"campaign_id": "cmp_2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"status": "sending",
"estimated_contacts": 312,
"estimated_cost_cents": 1934,
"estimated_days": 1,
"excluded": {
"no_consent": 41,
"suppressed": 3
},
"quiet_hours": false
}curl -X POST "https://boavoz.com/api/v1/campaigns" \
-H "Authorization: Bearer $BOAVOZ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3b9f2c8e-5a1d-4e7b-9c6f-2d8a4e1b7c90" \
-d '{
"bot_id": "bot_7c9e6679-7425-40de-944b-e07fc1f90ae7",
"kind": "utility_reminder",
"name": "Lembrete teste",
"template": {
"name": "lembrete_consulta",
"language": "pt_BR"
},
"sends": [
{
"contact": "phone:5511999990000",
"send_at": "2026-10-20T18:00:00-03:00",
"variables": [
"Ruan"
]
}
]
}'{
"campaign_id": "cmp_2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"status": "scheduled",
"estimated_contacts": 1,
"estimated_cost_cents": 5,
"excluded": {},
"off_hours": 0
}| Campo | Regra | Erro |
|---|---|---|
Idempotency-Key | Cabeçalho obrigatório (uma chave por campanha). | 400 Idempotency-Key |
bot_id | Obrigatório; o chatbot precisa ter tamanho e WhatsApp conectado. | 403 forbidden_scope; 422 bot_without_size, channel_not_connected, bot_paused |
channel | Só whatsapp (o padrão): o Instagram não tem mensagem de marketing. | 400 channel |
kind | marketing ou utility_reminder. O modelo precisa ser da categoria certa. | 400 kind; 422 template_category_mismatch |
name | Opcional: o nome no painel (até 120 caracteres). | 400 name |
template | {name, language?, variables?}, de um modelo aprovado do número. Na campanha, cada variável é um texto ou {"type": "first_name", "fallback": "cliente"} (o primeiro nome do contato), até 20. | 400 template.name, template.variables; 422 prohibited_content |
audience | Só no marketing: tags (até 20), contacts (até 10.000 endereços) e external_ids (até 1.000). Pelo menos um. Ninguém apto: 422 empty_audience, com quem ficou de fora em details.excluded. | 400 audience… |
send_at | Só no marketing, opcional: agenda o envio (ISO 8601 com o fuso, até 60 dias à frente). | 400 send_at |
regulated | Só no marketing, opcional: true quando a campanha fala de bebida alcoólica ou remédio, e ela só vai para quem confirmou 18 anos. O BoaVoz também marca sozinho quando o modelo fala disso. | 400 regulated |
sends | Só nos lembretes: até 2.000 linhas {contact, send_at, variables}. send_at com o fuso, não no passado e até 90 dias à frente; variables, textos de até 200 caracteres sem quebra de linha, tantos quanto o modelo pede. | 400 sends[i].… |
Na resposta, excluded conta quem ficou de fora, por motivo: no_consent (sem aceite de novidades), suppressed (pediu para sair), minor e no_age (18+), country, no_phone e not_contact. estimated_cost_cents é a estimativa da tarifa da Meta.
details.field utility_consent e as linhas.GET/v1/campaigns/{id}
status: scheduled, sending, paused (com pause_reason), finished ou canceled. Os totais contam enviadas, entregues, lidas, respondidas, falhas, puladas por motivo e descadastros.
curl "https://boavoz.com/api/v1/campaigns/cmp_2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d" \
-H "Authorization: Bearer $BOAVOZ_API_KEY"{
"id": "cmp_2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"bot_id": "bot_7c9e6679-7425-40de-944b-e07fc1f90ae7",
"kind": "marketing",
"name": "Teste Postman",
"status": "finished",
"pause_reason": null,
"template": {
"name": "promo_sexta",
"language": "pt_BR",
"category": "marketing"
},
"scheduled_at": null,
"created_at": "2026-10-19T15:20:00.000Z",
"finished_at": "2026-10-19T15:31:12.000Z",
"estimated_contacts": 312,
"estimated_cost_cents": 1934,
"totals": {
"sent": 312,
"delivered": 305,
"read": 211,
"replied": 27,
"failed": 7,
"pending": 0,
"uncertain": 0,
"skipped_no_consent": 41,
"skipped_no_age": 0,
"skipped_suppressed": 3,
"opted_out": 2
}
}POST/v1/contacts/{contact}/consent
Grava o aceite de novidades no WhatsApp com a prova (onde, o texto e quando), e cria o contato se ele ainda não existe. Quem pediu para sair continua fora (opted_out: true): só um novo sim dele no chat tira do descadastro. granted: false retira o aceite e emite o webhook contact.opted_out.
curl -X POST "https://boavoz.com/api/v1/contacts/phone:5511999990000/consent" \
-H "Authorization: Bearer $BOAVOZ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3b9f2c8e-5a1d-4e7b-9c6f-2d8a4e1b7c90" \
-d '{
"bot_id": "bot_7c9e6679-7425-40de-944b-e07fc1f90ae7",
"channel": "whatsapp",
"granted": true,
"source": "Teste pelo Postman",
"text": "Aceito receber novidades e promoções por WhatsApp"
}'{
"consent_id": "cns_7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"contact_id": "ctc_9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
"opted_out": false
}| Campo | Regra | Erro |
|---|---|---|
bot_id | Obrigatório; o chatbot precisa ter WhatsApp conectado. | 403 forbidden_scope; 422 channel_not_connected |
channel | Só whatsapp (o padrão). Contato do Instagram: 400. | 400 channel, contact |
granted | true (aceitou) ou false (retirou). | 400 granted |
source | Obrigatório: onde a pessoa aceitou ou retirou (3 a 200 caracteres, ex.: cadastro no site). | 400 source |
text | Obrigatório: o texto que ela aceitou (10 a 1.000 caracteres). | 400 text |
collected_at | Opcional: quando foi (ISO 8601, no passado). Padrão: agora. | 400 collected_at |
Contato que pediu para apagar os dados: 422 contact_suppressed (só um novo sim dado por ele no chat vale).