Campanhas
Campanhas são disparos para muitos contatos de uma vez pelo WhatsApp Oficial (API da Meta), usando templates aprovados. São o caminho certo para promoções, novidades e avisos em massa — ao contrário de POST /messages, que usa o WhatsApp conectado por QR Code e serve para mensagens individuais.
Toda campanha passa pela revisão da equipe da TopTalking antes de sair. Aprovada, ela começa a enviar sozinha.
Antes de começar
Seção intitulada “Antes de começar”| Requisito | Onde resolver |
|---|---|
Um número do WhatsApp Oficial conectado. Ele é diferente dos canais conectados por QR Code e não aparece em GET /channels. |
No painel, em Campanhas. Sem ele, POST /campaigns responde 422 channel_unavailable. |
Pelo menos um template pronto (ready: true): aprovado pela TopTalking e pela Meta. |
No painel, em Campanhas → Templates. Pela API você só lista os templates. |
| Opt-in dos destinatários: só envie para quem aceitou receber mensagens da empresa. | Na sua base. Veja Boas práticas no WhatsApp. |
Ciclo de vida
Seção intitulada “Ciclo de vida” POST /campaigns │ ▼ ┌──────────────────┐ a revisão pediu ajustes ┌───────────────────┐ │ pending_approval │ ──────────────────────► │ changes_requested │ └──────────────────┘ └───────────────────┘ │ aprovada pela TopTalking ▼ ┌──────────┐ em ~1 min ┌─────────┐ terminou ┌───────────┐ │ approved │ ────────► │ running │ ───────► │ completed │ └──────────┘ └─────────┘ └───────────┘ │ │ ▲ │ POST .../pause │ │ POST .../resume ▼ ▼ │ ┌──────────────────────────┐ │ │ paused │ ──┘ └──────────────────────────┘
failed: o envio não pôde continuarstatus |
Significado |
|---|---|
pending_approval |
Criada e aguardando a revisão da TopTalking. É assim que toda campanha criada pela API nasce. |
changes_requested |
A revisão pediu ajustes: leia review_notes, corrija e crie uma nova campanha. |
approved |
Aprovada. Começa a enviar sozinha em até ~1 minuto. |
running |
Enviando. Acompanhe em totals. |
paused |
Pausada por você (ou pelo painel). Retome com POST /campaigns/{campaign_id}/resume. |
completed |
Todos os destinatários foram processados. |
failed |
O envio não pôde continuar. |
draft |
Rascunho criado no painel e ainda não enviado para revisão. |
Para ser avisado de cada mudança, assine o webhook campaign.status_updated.
- Template pronto:
template.name(etemplate.language, padrãopt_BR) precisa ser de um template comready: true. Template inexistente →404 resource_not_found; existente mas não pronto →400 invalid_field(param: "template"). - Variáveis na ordem:
variablesde cada destinatário preenche{{1}},{{2}}… do corpo do template, na ordem, e precisa ter exatamentevariables_countitens. Números são convertidos em texto. - Botão de link com variável: se o template tem um botão com URL variável (ex.:
https://site.com/checkout/{{1}}), o valor do link vai no fim devariables, depois das variáveis do corpo. Mande só a parte que entra no lugar de{{1}}(ex.:86). Ele já está contado emvariables_count. - Até 5.000 destinatários por requisição (corpo de até 1 MB). Para listas maiores, crie várias campanhas.
- Telefones repetidos na mesma requisição recebem a mensagem uma vez só.
skip_already_sent: truepula quem já recebeu alguma campanha da organização. Se isso deixar a lista vazia, a resposta é400 invalid_field(param: "recipients").- Preço congelado: o preço por mensagem (
pricing.unit_price) é o da tabela da sua organização no momento da criação e não muda depois.pricing.estimated_total= preço × destinatários;pricing.charged_total= preço × mensagens já enviadas. Simule antes comPOST /campaigns/quote. - Pausar e retomar: só dá para pausar campanhas
approvedourunning, e retomar campanhaspaused. Fora disso →409 invalid_state.
Passo a passo
Seção intitulada “Passo a passo”-
Escolha um template pronto com
GET /templates?ready=true. Anotename,languageevariables_count(quantos valores cada destinatário precisa).Janela do terminal curl "https://platform.toptalking.com.br/v1/templates?ready=true" \-H "Authorization: Bearer $TOPTALKING_API_KEY" -
Simule o preço com
POST /campaigns/quote, informando o template e quantos destinatários você terá. Nada é criado.Janela do terminal curl https://platform.toptalking.com.br/v1/campaigns/quote \-H "Authorization: Bearer $TOPTALKING_API_KEY" \-H "Content-Type: application/json" \-d '{ "template": { "name": "promo_black_friday", "language": "pt_BR" }, "recipients_count": 1500 }' -
Crie a campanha com
POST /campaigns. EnvieIdempotency-Keypara uma retentativa não criar outra campanha igual. A resposta201vem comstatus: "pending_approval".Janela do terminal curl https://platform.toptalking.com.br/v1/campaigns \-H "Authorization: Bearer $TOPTALKING_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: black-friday-2026-lote-1" \-d '{"name": "Black Friday 2026","template": { "name": "promo_black_friday", "language": "pt_BR" },"recipients": [{ "phone": "5511999998888", "name": "Maria Souza", "variables": ["Maria", "30%"] },{ "phone": "5521987654321", "name": "João Pereira", "variables": ["João", "30%"] },{ "phone": "5531991234567", "name": "Ana Lima", "variables": ["Ana", "30%"] }],"skip_already_sent": false}' -
Acompanhe o status. Assine o webhook
campaign.status_updatedou consulteGET /campaigns/{campaign_id}de tempos em tempos (a cada 1–5 minutos é suficiente). Se vierchanges_requested, leiareview_notes. -
Veja quem recebeu com
GET /campaigns/{campaign_id}/recipients— filtre porstatus=failedpara ver as falhas e o motivo emerror.
Exemplo completo
Seção intitulada “Exemplo completo”// Node 18+. Rode no seu servidor: node campanha.mjsconst API = 'https://platform.toptalking.com.br/v1';const headers = { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json',};
async function api(path, init = {}) { const res = await fetch(`${API}${path}`, { ...init, headers: { ...headers, ...init.headers } }); const body = await res.json(); if (!res.ok) throw new Error(`${res.status} ${body.error.code} (${body.error.param ?? '-'}): ${body.error.message}`); return body;}
const clientes = [ { phone: '5511999998888', nome: 'Maria Souza' }, { phone: '5521987654321', nome: 'João Pereira' }, { phone: '5531991234567', nome: 'Ana Lima' },];
// 1. Template prontoconst { data: templates } = await api('/templates?ready=true');const template = templates.find((t) => t.name === 'promo_black_friday');if (!template) throw new Error('Template não está pronto: confira no painel, em Campanhas → Templates.');
// 2. Preçoconst orcamento = await api('/campaigns/quote', { method: 'POST', body: JSON.stringify({ template: { name: template.name, language: template.language }, recipients_count: clientes.length, }),});console.log(`Custo estimado: ${orcamento.currency} ${orcamento.total}`);
// 3. Criar (as variables têm exatamente template.variables_count itens)const campanha = await api('/campaigns', { method: 'POST', headers: { 'Idempotency-Key': 'black-friday-2026-lote-1' }, body: JSON.stringify({ name: 'Black Friday 2026', template: { name: template.name, language: template.language }, recipients: clientes.map((c) => ({ phone: c.phone, name: c.nome, variables: [c.nome.split(' ')[0], '30%'], })), skip_already_sent: false, }),});console.log('Campanha criada:', campanha.id, campanha.status); // pending_approval
// 4. Acompanhar (ou use o webhook campaign.status_updated)const FINAIS = ['completed', 'failed', 'changes_requested'];let atual = campanha;while (!FINAIS.includes(atual.status)) { await new Promise((r) => setTimeout(r, 60_000)); // 1 minuto atual = await api(`/campaigns/${campanha.id}`); console.log(atual.status, atual.totals);}if (atual.status === 'changes_requested') console.log('Ajustes pedidos:', atual.review_notes);
// 5. Falhasconst { data: falhas } = await api(`/campaigns/${campanha.id}/recipients?status=failed&limit=100`);for (const f of falhas) console.log(f.phone, f.error);import osimport time
import requests
API = "https://platform.toptalking.com.br/v1"HEADERS = {"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}
def api(method, path, **kwargs): res = requests.request(method, f"{API}{path}", headers={**HEADERS, **kwargs.pop("headers", {})}, timeout=60, **kwargs) body = res.json() if not res.ok: e = body["error"] raise RuntimeError(f"{res.status_code} {e['code']} ({e.get('param')}): {e['message']}") return body
clientes = [ {"phone": "5511999998888", "nome": "Maria Souza"}, {"phone": "5521987654321", "nome": "João Pereira"}, {"phone": "5531991234567", "nome": "Ana Lima"},]
# 1. Template prontotemplates = api("GET", "/templates", params={"ready": "true"})["data"]template = next(t for t in templates if t["name"] == "promo_black_friday")ref = {"name": template["name"], "language": template["language"]}
# 2. Preçoorcamento = api("POST", "/campaigns/quote", json={"template": ref, "recipients_count": len(clientes)})print("Custo estimado:", orcamento["currency"], orcamento["total"])
# 3. Criarcampanha = api( "POST", "/campaigns", headers={"Idempotency-Key": "black-friday-2026-lote-1"}, json={ "name": "Black Friday 2026", "template": ref, "recipients": [ {"phone": c["phone"], "name": c["nome"], "variables": [c["nome"].split()[0], "30%"]} for c in clientes ], "skip_already_sent": False, },)print("Campanha criada:", campanha["id"], campanha["status"])
# 4. Acompanhar (ou use o webhook campaign.status_updated)while campanha["status"] not in ("completed", "failed", "changes_requested"): time.sleep(60) campanha = api("GET", f"/campaigns/{campanha['id']}") print(campanha["status"], campanha["totals"])
# 5. Falhasfalhas = api("GET", f"/campaigns/{campanha['id']}/recipients", params={"status": "failed", "limit": 100})for f in falhas["data"]: print(f["phone"], f["error"])O objeto template
Seção intitulada “O objeto template”{ "id": "875269ea-ba67-436d-81b4-0d209df54520", "object": "template", "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING", "status": "APPROVED", "review_status": "approved", "ready": true, "body": "Oi {{1}}! A Black Friday da Loja Aurora chegou: {{2}} de desconto em todo o site até domingo. Responda SAIR para não receber mais.", "variables_count": 2, "header_type": "IMAGE", "rejected_reason": null, "created_at": "2026-10-20T14:00:00Z"}| Campo | Tipo | Descrição |
|---|---|---|
id |
UUID | ID do template. |
object |
string | Sempre template. |
name |
string | Nome do template. É o que você envia em template.name. |
language |
string | Idioma (ex.: pt_BR). É o que você envia em template.language. |
category |
string ou null |
Categoria na Meta: MARKETING, UTILITY ou AUTHENTICATION. O preço por mensagem depende dela. |
status |
string ou null |
Status na Meta: APPROVED, PENDING, REJECTED, PAUSED ou DISABLED. |
review_status |
string | Revisão interna da TopTalking: approved, pending ou rejected. |
ready |
booleano | true = pode ser usado em campanhas (aprovado pela TopTalking e pela Meta). |
body |
string ou null |
Texto do corpo, com as variáveis {{1}}, {{2}}… |
variables_count |
inteiro | Quantos itens cada destinatário precisa em variables: as variáveis do corpo mais 1 por botão de link com variável. |
header_type |
string ou null |
Tipo do cabeçalho: TEXT, IMAGE, VIDEO, DOCUMENT ou null (sem cabeçalho). |
rejected_reason |
string ou null |
Motivo da rejeição, quando houver. |
created_at |
data | Criação. |
Listar templates
Seção intitulada “Listar templates”GET/v1/templates
Os templates de mensagem do WhatsApp Oficial da organização. Só os com ready: true podem ser usados em campanhas. Os templates são criados e enviados para aprovação no painel, em Campanhas → Templates.
| Parâmetro | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
ready |
query | booleano | não | true devolve só os templates prontos para uso. |
A lista vem inteira numa página (has_more: false).
curl https://platform.toptalking.com.br/v1/templates \ -H "Authorization: Bearer $TOPTALKING_API_KEY"const res = await fetch('https://platform.toptalking.com.br/v1/templates?ready=true', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` },});const { data: prontos } = await res.json();for (const t of prontos) console.log(t.name, t.language, `${t.variables_count} variáveis`);import osimport requests
res = requests.get( "https://platform.toptalking.com.br/v1/templates", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"ready": "true"}, timeout=30,)res.raise_for_status()prontos = res.json()["data"]Resposta 200
Seção intitulada “Resposta 200”Sem filtro, vêm também os templates que ainda não estão prontos:
{ "object": "list", "data": [ { "id": "875269ea-ba67-436d-81b4-0d209df54520", "object": "template", "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING", "status": "APPROVED", "review_status": "approved", "ready": true, "body": "Oi {{1}}! A Black Friday da Loja Aurora chegou: {{2}} de desconto em todo o site até domingo. Responda SAIR para não receber mais.", "variables_count": 2, "header_type": "IMAGE", "rejected_reason": null, "created_at": "2026-10-20T14:00:00Z" }, { "id": "3acdaef8-6224-43e8-aa29-9de336a75b7b", "object": "template", "name": "lembrete_pagamento", "language": "pt_BR", "category": "UTILITY", "status": "PENDING", "review_status": "approved", "ready": false, "body": "Olá {{1}}, o boleto do pedido {{2}} vence em {{3}}. Precisa de ajuda? É só responder esta mensagem.", "variables_count": 3, "header_type": null, "rejected_reason": null, "created_at": "2026-11-10T19:12:00Z" } ], "has_more": false, "next_cursor": null}Só os gerais: 401 (chave) e 429 (limite por minuto). Veja Erros.
O objeto campanha
Seção intitulada “O objeto campanha”{ "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "running", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 1, "sent": 2, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0.7 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": null}| Campo | Tipo | Descrição |
|---|---|---|
id |
UUID | ID da campanha. |
object |
string | Sempre campaign. |
name |
string | Nome da campanha. |
status |
string | Veja o ciclo de vida. |
review_notes |
string ou null |
Observação da revisão da TopTalking (ex.: o que ajustar em changes_requested). |
template |
objeto | name, language e category do template usado. |
totals.recipients |
inteiro | Total de destinatários (= pending + sent + failed). |
totals.pending |
inteiro | Ainda não enviados. |
totals.sent |
inteiro | Enviados (inclui os entregues e os lidos). |
totals.delivered |
inteiro | Entregues no celular (inclui os lidos). |
totals.read |
inteiro | Lidos. |
totals.failed |
inteiro | Falharam. |
pricing.unit_price |
número | Preço por mensagem, congelado na criação. |
pricing.currency |
string | Moeda (BRL). |
pricing.estimated_total |
número | unit_price × destinatários. |
pricing.charged_total |
número | unit_price × mensagens já enviadas (arredondado em 2 casas). |
created_at |
data | Criação. |
submitted_at |
data ou null |
Quando foi enviada para revisão. |
reviewed_at |
data ou null |
Quando a TopTalking revisou. |
started_at |
data ou null |
Quando o envio começou. |
completed_at |
data ou null |
Quando terminou. |
Simular preço
Seção intitulada “Simular preço”POST/v1/campaigns/quote
Calcula quanto uma campanha vai custar, pela tabela de preço da sua organização, sem criar nada. Use antes de criar, para mostrar o valor a quem vai aprovar o disparo.
| Parâmetro | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
template.name |
corpo | string | sim | Nome do template. |
template.language |
corpo | string | não | Idioma do template. Padrão: pt_BR. |
recipients_count |
corpo | inteiro | sim | Quantos destinatários (1 a 100.000). |
curl https://platform.toptalking.com.br/v1/campaigns/quote \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template": { "name": "promo_black_friday", "language": "pt_BR" }, "recipients_count": 1500 }'const res = await fetch('https://platform.toptalking.com.br/v1/campaigns/quote', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ template: { name: 'promo_black_friday', language: 'pt_BR' }, recipients_count: 1500, }),});const orcamento = await res.json();console.log(`${orcamento.recipients} mensagens × R$ ${orcamento.unit_price} = R$ ${orcamento.total}`);import osimport requests
res = requests.post( "https://platform.toptalking.com.br/v1/campaigns/quote", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, json={ "template": {"name": "promo_black_friday", "language": "pt_BR"}, "recipients_count": 1500, }, timeout=30,)res.raise_for_status()orcamento = res.json()Resposta 200
Seção intitulada “Resposta 200”{ "object": "campaign_quote", "category": "MARKETING", "recipients": 1500, "unit_price": 0.35, "currency": "BRL", "total": 525}| Campo | Tipo | Descrição |
|---|---|---|
object |
string | Sempre campaign_quote. |
category |
string ou null |
Categoria do template (define o preço). |
recipients |
inteiro | Quantidade simulada. |
unit_price |
número | Preço por mensagem hoje. |
currency |
string | Moeda (BRL). |
total |
número | unit_price × recipients. |
Esta é a única rota POST liberada para chaves somente leitura: ela só calcula, não cria nada.
| HTTP | code |
Quando acontece |
|---|---|---|
| 400 | missing_field, invalid_field |
Faltou template ou recipients_count, ou recipients_count fora de 1–100.000. |
| 404 | resource_not_found |
Não existe template com esse name e language. |
Criar campanha
Seção intitulada “Criar campanha”POST/v1/campaigns
Cria um disparo pelo WhatsApp Oficial com um template pronto e envia para a revisão da TopTalking. A campanha nasce pending_approval; ao ser aprovada, o envio começa sozinho (approved → running → completed). O preço por mensagem fica congelado neste momento.
| Parâmetro | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
name |
corpo | string | sim | Nome da campanha (até 120 caracteres). |
template.name |
corpo | string | sim | Nome de um template com ready: true. |
template.language |
corpo | string | não | Idioma do template. Padrão: pt_BR. |
recipients |
corpo | lista | sim | De 1 a 5.000 destinatários. |
recipients[].phone |
corpo | string | sim | Telefone com DDI e DDD (ex.: 5511999998888). |
recipients[].name |
corpo | string | não | Nome do destinatário. |
recipients[].variables |
corpo | lista de strings | se o template tiver variáveis | Valores de {{1}}, {{2}}… na ordem. Precisa ter exatamente variables_count itens. |
skip_already_sent |
corpo | booleano | não | true pula quem já recebeu alguma campanha desta organização. Padrão: false. |
Idempotency-Key |
cabeçalho | string | não (recomendado) | Evita criar a mesma campanha duas vezes numa retentativa. |
curl https://platform.toptalking.com.br/v1/campaigns \ -H "Authorization: Bearer $TOPTALKING_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: black-friday-2026-lote-1" \ -d '{ "name": "Black Friday 2026", "template": { "name": "promo_black_friday", "language": "pt_BR" }, "recipients": [ { "phone": "5511999998888", "name": "Maria Souza", "variables": ["Maria", "30%"] }, { "phone": "5521987654321", "name": "João Pereira", "variables": ["João", "30%"] }, { "phone": "5531991234567", "name": "Ana Lima", "variables": ["Ana", "30%"] } ], "skip_already_sent": false }'const res = await fetch('https://platform.toptalking.com.br/v1/campaigns', { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'black-friday-2026-lote-1', }, body: JSON.stringify({ name: 'Black Friday 2026', template: { name: 'promo_black_friday', language: 'pt_BR' }, recipients: [ { phone: '5511999998888', name: 'Maria Souza', variables: ['Maria', '30%'] }, { phone: '5521987654321', name: 'João Pereira', variables: ['João', '30%'] }, { phone: '5531991234567', name: 'Ana Lima', variables: ['Ana', '30%'] }, ], skip_already_sent: false, }),});const campanha = await res.json();if (!res.ok) throw new Error(`${campanha.error.code} (${campanha.error.param}): ${campanha.error.message}`);console.log(campanha.id, campanha.status); // "pending_approval"import osimport requests
res = requests.post( "https://platform.toptalking.com.br/v1/campaigns", headers={ "Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}", "Idempotency-Key": "black-friday-2026-lote-1", }, json={ "name": "Black Friday 2026", "template": {"name": "promo_black_friday", "language": "pt_BR"}, "recipients": [ {"phone": "5511999998888", "name": "Maria Souza", "variables": ["Maria", "30%"]}, {"phone": "5521987654321", "name": "João Pereira", "variables": ["João", "30%"]}, {"phone": "5531991234567", "name": "Ana Lima", "variables": ["Ana", "30%"]}, ], "skip_already_sent": False, }, timeout=60,)campanha = res.json()if not res.ok: erro = campanha["error"] raise RuntimeError(f"{erro['code']} ({erro.get('param')}): {erro['message']}")Resposta 201
Seção intitulada “Resposta 201”{ "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "pending_approval", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 3, "sent": 0, "delivered": 0, "read": 0, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": null, "started_at": null, "completed_at": null}| HTTP | code |
param |
Quando acontece |
|---|---|---|---|
| 400 | missing_field |
name, template ou recipients |
Faltou um campo obrigatório. |
| 400 | invalid_field |
template |
O template existe, mas não está pronto (ready: false). |
| 400 | invalid_field |
recipients[3].variables |
A quantidade de variables desse destinatário é diferente de variables_count. |
| 400 | invalid_field |
recipients |
Mais de 5.000 destinatários, lista vazia ou, com skip_already_sent: true, todos já receberam alguma campanha. |
| 400 | invalid_phone |
recipients[n].phone |
Telefone inválido em um destinatário. |
| 403 | read_only_key |
— | Chave somente leitura. |
| 404 | resource_not_found |
template |
Não existe template com esse name e language. |
| 409 | idempotency_conflict |
— | Idempotency-Key reutilizada com outro corpo. |
| 422 | channel_unavailable |
— | A organização não tem número do WhatsApp Oficial conectado. |
{ "error": { "type": "invalid_request_error", "code": "invalid_field", "message": "O template \"promo_black_friday\" tem 2 variáveis; `recipients[3].variables` trouxe 1.", "param": "recipients[3].variables", "request_id": "req_6f1d2c3b4a5e" }}{ "error": { "type": "channel_error", "code": "channel_unavailable", "message": "Nenhum número do WhatsApp Oficial conectado. Conecte no painel em Campanhas.", "request_id": "req_6f1d2c3b4a5e" }}Listar campanhas
Seção intitulada “Listar campanhas”GET/v1/campaigns
As campanhas da organização, das mais recentes para as mais antigas, com paginação.
| Parâmetro | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
status |
query | string | não | Só campanhas neste status (draft, pending_approval, approved, changes_requested, running, paused, completed ou failed). |
limit |
query | inteiro | não | Itens por página, 1 a 100 (padrão 20). |
cursor |
query | string | não | next_cursor da página anterior. |
curl "https://platform.toptalking.com.br/v1/campaigns?limit=10" \ -H "Authorization: Bearer $TOPTALKING_API_KEY"const res = await fetch('https://platform.toptalking.com.br/v1/campaigns?status=running', { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` },});const { data: enviando } = await res.json();import osimport requests
res = requests.get( "https://platform.toptalking.com.br/v1/campaigns", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"limit": 10}, timeout=30,)res.raise_for_status()campanhas = res.json()["data"]Resposta 200
Seção intitulada “Resposta 200”{ "object": "list", "data": [ { "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "running", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 1, "sent": 2, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0.7 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": null }, { "id": "4815f77a-bd81-4e28-bded-100b6a109cad", "object": "campaign", "name": "Aviso de feriado - Finados", "status": "completed", "review_notes": null, "template": { "name": "aviso_horario_feriado", "language": "pt_BR", "category": "UTILITY" }, "totals": { "recipients": 1500, "pending": 0, "sent": 1480, "delivered": 1420, "read": 900, "failed": 20 }, "pricing": { "unit_price": 0.08, "currency": "BRL", "estimated_total": 120, "charged_total": 118.4 }, "created_at": "2026-10-30T13:10:00Z", "submitted_at": "2026-10-30T13:10:00Z", "reviewed_at": "2026-10-30T14:02:00Z", "started_at": "2026-10-30T14:02:55Z", "completed_at": "2026-10-30T14:41:18Z" } ], "has_more": false, "next_cursor": null}Só os gerais: 401 (chave) e 429 (limite por minuto). Veja Erros.
Buscar campanha
Seção intitulada “Buscar campanha”GET/v1/campaigns/{campaign_id}
Uma campanha pelo ID, com status, contadores e preço atualizados. Use para acompanhar o envio quando não estiver usando o webhook campaign.status_updated.
| Parâmetro | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
campaign_id |
caminho | UUID | sim | ID da campanha. |
curl https://platform.toptalking.com.br/v1/campaigns/f2143187-f4f8-43b0-9c03-57a26c575f41 \ -H "Authorization: Bearer $TOPTALKING_API_KEY"const campaignId = 'f2143187-f4f8-43b0-9c03-57a26c575f41';const res = await fetch(`https://platform.toptalking.com.br/v1/campaigns/${campaignId}`, { headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` },});const campanha = await res.json();const { recipients, pending } = campanha.totals;console.log(`${campanha.status}: ${recipients - pending}/${recipients} processados`);import osimport requests
campaign_id = "f2143187-f4f8-43b0-9c03-57a26c575f41"res = requests.get( f"https://platform.toptalking.com.br/v1/campaigns/{campaign_id}", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30,)res.raise_for_status()campanha = res.json()Resposta 200
Seção intitulada “Resposta 200”{ "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "completed", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 0, "sent": 3, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 1.05 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": "2026-11-11T16:21:30Z"}| HTTP | code |
Quando acontece |
|---|---|---|
| 404 | resource_not_found |
A campanha não existe ou é de outra organização. |
Destinatários da campanha
Seção intitulada “Destinatários da campanha”GET/v1/campaigns/{campaign_id}/recipients
O status de envio de cada destinatário, na ordem em que foram enviados à API, com paginação. Filtre por status=failed para ver quem não recebeu e o motivo.
| Parâmetro | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
campaign_id |
caminho | UUID | sim | ID da campanha. |
status |
query | string | não | pending, sending, sent, delivered, read ou failed. |
limit |
query | inteiro | não | Itens por página, 1 a 100 (padrão 20). |
cursor |
query | string | não | next_cursor da página anterior. |
curl "https://platform.toptalking.com.br/v1/campaigns/f2143187-f4f8-43b0-9c03-57a26c575f41/recipients?limit=100" \ -H "Authorization: Bearer $TOPTALKING_API_KEY"// Percorre todas as páginas de destinatários que falharamconst API = 'https://platform.toptalking.com.br/v1';const headers = { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` };const campaignId = 'f2143187-f4f8-43b0-9c03-57a26c575f41';
const falhas = [];let cursor = null;do { const url = new URL(`${API}/campaigns/${campaignId}/recipients`); url.searchParams.set('status', 'failed'); url.searchParams.set('limit', '100'); if (cursor) url.searchParams.set('cursor', cursor); const page = await (await fetch(url, { headers })).json(); falhas.push(...page.data); cursor = page.has_more ? page.next_cursor : null;} while (cursor);import osimport requests
campaign_id = "f2143187-f4f8-43b0-9c03-57a26c575f41"res = requests.get( f"https://platform.toptalking.com.br/v1/campaigns/{campaign_id}/recipients", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, params={"limit": 100}, timeout=30,)res.raise_for_status()for d in res.json()["data"]: print(d["phone"], d["status"], d["error"] or "")Resposta 200
Seção intitulada “Resposta 200”{ "object": "list", "data": [ { "id": "434c30a3-7853-4b6f-9b21-e02da711774a", "object": "campaign_recipient", "phone": "5511999998888", "name": "Maria Souza", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "variables": ["Maria", "30%"], "status": "read", "error": null, "sent_at": "2026-11-11T16:20:43Z", "delivered_at": "2026-11-11T16:20:45Z", "read_at": "2026-11-11T16:24:10Z" }, { "id": "5c80c4da-1858-41ca-8969-746ea12fdc01", "object": "campaign_recipient", "phone": "5521987654321", "name": "João Pereira", "contact_id": "721bfe94-e549-4354-b90c-a8d679e9b271", "variables": ["João", "30%"], "status": "delivered", "error": null, "sent_at": "2026-11-11T16:20:44Z", "delivered_at": "2026-11-11T16:20:47Z", "read_at": null }, { "id": "a1138591-7dd7-45cc-8a72-9f492f087c77", "object": "campaign_recipient", "phone": "5531991234567", "name": "Ana Lima", "contact_id": "6586daec-7197-478e-b0ff-5b6659c11d26", "variables": ["Ana", "30%"], "status": "sent", "error": null, "sent_at": "2026-11-11T16:21:29Z", "delivered_at": null, "read_at": null } ], "has_more": false, "next_cursor": null}| Campo | Tipo | Descrição |
|---|---|---|
id |
UUID | ID do destinatário na campanha. |
object |
string | Sempre campaign_recipient. |
phone |
string | Telefone, só dígitos com DDI. |
name |
string ou null |
Nome enviado na criação. |
contact_id |
UUID ou null |
Contato (conversa) correspondente no painel, quando houver. |
variables |
lista de strings | Valores usados nas variáveis do template. |
status |
string | pending, sending, sent, delivered, read ou failed. |
error |
string ou null |
Motivo da falha, quando status é failed. |
sent_at |
data ou null |
Quando foi enviado. |
delivered_at |
data ou null |
Quando chegou no celular. |
read_at |
data ou null |
Quando foi lido. |
| HTTP | code |
Quando acontece |
|---|---|---|
| 404 | resource_not_found |
A campanha não existe ou é de outra organização. |
Pausar campanha
Seção intitulada “Pausar campanha”POST/v1/campaigns/{campaign_id}/pause
Interrompe o envio de uma campanha approved ou running. Quem já recebeu não é afetado; os pendentes esperam até você retomar.
| Parâmetro | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
campaign_id |
caminho | UUID | sim | ID da campanha. |
curl -X POST https://platform.toptalking.com.br/v1/campaigns/f2143187-f4f8-43b0-9c03-57a26c575f41/pause \ -H "Authorization: Bearer $TOPTALKING_API_KEY"const campaignId = 'f2143187-f4f8-43b0-9c03-57a26c575f41';const res = await fetch(`https://platform.toptalking.com.br/v1/campaigns/${campaignId}/pause`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` },});const campanha = await res.json();if (res.status === 409) console.log('Não dá para pausar agora:', campanha.error.message);import osimport requests
campaign_id = "f2143187-f4f8-43b0-9c03-57a26c575f41"res = requests.post( f"https://platform.toptalking.com.br/v1/campaigns/{campaign_id}/pause", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30,)campanha = res.json()Resposta 200
Seção intitulada “Resposta 200”{ "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "paused", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 1, "sent": 2, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0.7 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": null}| HTTP | code |
Quando acontece |
|---|---|---|
| 403 | read_only_key |
Chave somente leitura. |
| 404 | resource_not_found |
A campanha não existe ou é de outra organização. |
| 409 | invalid_state |
A campanha não está approved nem running (ex.: ainda em revisão, já pausada ou concluída). |
{ "error": { "type": "conflict_error", "code": "invalid_state", "message": "Só é possível pausar campanhas aprovadas ou em envio (status atual: pending_approval).", "request_id": "req_6f1d2c3b4a5e" }}Retomar campanha
Seção intitulada “Retomar campanha”POST/v1/campaigns/{campaign_id}/resume
Volta a enviar uma campanha paused, continuando de onde parou (só os pendentes recebem).
| Parâmetro | Onde | Tipo | Obrigatório | Descrição |
|---|---|---|---|---|
campaign_id |
caminho | UUID | sim | ID da campanha. |
curl -X POST https://platform.toptalking.com.br/v1/campaigns/f2143187-f4f8-43b0-9c03-57a26c575f41/resume \ -H "Authorization: Bearer $TOPTALKING_API_KEY"const campaignId = 'f2143187-f4f8-43b0-9c03-57a26c575f41';const res = await fetch(`https://platform.toptalking.com.br/v1/campaigns/${campaignId}/resume`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TOPTALKING_API_KEY}` },});const campanha = await res.json(); // campanha.status === "running"import osimport requests
campaign_id = "f2143187-f4f8-43b0-9c03-57a26c575f41"res = requests.post( f"https://platform.toptalking.com.br/v1/campaigns/{campaign_id}/resume", headers={"Authorization": f"Bearer {os.environ['TOPTALKING_API_KEY']}"}, timeout=30,)campanha = res.json()Resposta 200
Seção intitulada “Resposta 200”A campanha, de volta ao envio:
{ "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "running", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 1, "sent": 2, "delivered": 2, "read": 1, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0.7 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": "2026-11-11T16:20:41Z", "completed_at": null}| HTTP | code |
Quando acontece |
|---|---|---|
| 403 | read_only_key |
Chave somente leitura. |
| 404 | resource_not_found |
A campanha não existe ou é de outra organização. |
| 409 | invalid_state |
A campanha não está paused. |