Campanhas
Uma campanha envia um template aprovado para um público: todos os contatos que um filtro seleciona, com as variáveis de cada um preenchidas. Pela API você monta, confere, lança e acompanha a campanha — o envio em si roda num processo à parte, no ritmo que você definir, sem gastar o limite de envio da sua chave.
O caminho de uma campanha
materializing é a montagem da lista de destinatários: o público é congelado no
lançamento, e quem entrar na base depois não recebe. Uma campanha pode ainda terminar em
canceled ou, quando ninguém do público pode recebê-la, em failed.
1. Criar o rascunho
curl -X POST https://api.joinotify.com/broadcasts \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{
"name": "Black Friday",
"phoneNumberId": "106540352242922",
"templateName": "promo_black_friday",
"templateLanguage": "pt_BR",
"audienceId": "cm1aud1ence01",
"variables": {
"body": {
"1": { "source": "contact", "key": "first_name", "fallback": "cliente" },
"2": { "source": "static", "value": "BLACK20" }
}
},
"sendWindow": { "days": [1, 2, 3, 4, 5], "start": "09:00", "end": "20:00" },
"timezone": "America/Sao_Paulo"
}'
Só name é obrigatório; o resto pode vir depois, por PATCH. Nada é conferido ao salvar
— a conferência acontece na estimativa, no teste e no lançamento.
Variáveis. Cada marcador do template precisa de uma fonte: contact (um dado do
contato, como first_name), attribute (um campo personalizado) ou static (um valor
fixo). As duas primeiras exigem um fallback para quem não tem o dado. Um template com
cabeçalho de mídia precisa de headerMedia — suba o arquivo em POST /broadcasts/media
e use o id.
Público. Um público salvo (audienceId) ou um filtro só desta campanha
(audienceFilter), com a mesma árvore de condições. Crie públicos reaproveitáveis em
POST /audiences e confira quem eles pegam em POST /audiences/preview.
2. Estimar e testar
POST /broadcasts/{id}/estimate confere a campanha inteira e responde quem ela
alcançaria agora:
{
"data": {
"recipients": 4800,
"total": 5230,
"skipped": { "opted_out": 120, "no_consent": 290, "suppressed": 20 },
"cap": 50000,
"firstCampaign": false,
"overCap": 0,
"remainingQuota": 18000,
"portfolioLimit": 10000,
"estimatedCost": { "micros": 297600000, "currency": "brl", "byCountry": [], "unpriced": 0 }
}
}
Depois, mande a campanha para você mesmo com POST /broadcasts/{id}/test e
{ "to": "+5541987111527" }. O teste sai na hora, fora das métricas, e mostra as
variáveis preenchidas com os valores reserva.
Só contatos com consentimento (opted_in) e fora da lista de supressão. Templates de
utilidade não dependem de consentimento, mas continuam pulando números inválidos. Veja
Contatos e consentimento.
3. Lançar
curl -X POST https://api.joinotify.com/broadcasts/cm1bro4dc4st01/launch \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "scheduledAt": "2026-11-27T12:00:00-03:00" }'
Sem scheduledAt, a campanha começa na hora. A resposta é 202. O lançamento recusa com
422:
type | Por quê |
|---|---|
audience_empty | Ninguém do público pode receber |
broadcast_limit_reached | O público passa do teto de destinatários (limit diz qual) |
quota_exceeded | O plano não tem mais mensagens neste mês |
O teto cresce com o histórico. A primeira campanha da conta vai para até 1.000 pessoas; as seguintes, para até dez vezes a maior campanha já concluída — sempre dentro do limite do plano. É o que protege a qualidade de um número novo de um disparo grande demais.
Editar uma campanha agendada a devolve para rascunho: é preciso lançar de novo.
4. Acompanhar
GET /broadcasts/{id}/stats— contadores, taxas, cliques por botão, falhas agrupadas, custo por país e a qualidade do número antes e agora.GET /broadcasts/{id}/timeseries— a evolução em faixas de 15 minutos.GET /broadcasts/{id}/recipientse/export— cada destinatário, com o status e o erro.- Os eventos
broadcast.pausedebroadcast.completedno seu webhook.
Pausas automáticas
Uma campanha para sozinha quando continuar prejudicaria o número: qualidade vermelha,
template pausado pela Meta, conta restrita, cota do plano esgotada, erros demais seguidos.
O motivo fica em pauseReason e chega no evento broadcast.paused — a tabela completa
está no catálogo de eventos.
Duas pausas se resolvem sozinhas e trazem resumeAfter: send_window (fora da janela de
envio) e throughput (a Meta segurou a vazão; tenta de novo em 15 minutos, 1 hora e 4
horas). As outras esperam alguém corrigir a causa e chamar POST /broadcasts/{id}/resume.
Envios sem resposta
Quando a Meta não responde a um envio, o destinatário fica unknown. Ele não é
repetido sozinho: a mensagem pode ter chegado, e repetir mandaria duas vezes. Se você
decidir reenviar, POST /broadcasts/{id}/resend-unknown com { "confirm": true } devolve
esses destinatários à fila.
Escopo da chave
Uma API key restrita a alguns números só enxerga e cria campanhas nesses números. Os
públicos salvos são da conta inteira: /audiences responde 403 para ela.
O público de uma campanha estimada ou lançada por essa chave — salvo ou digitado como filtro —
fica restrito aos contatos que têm conversa nos números dela, e o recorte é congelado no
lançamento, junto com o público. No envio de teste, um contactId fora desse recorte responde
404. Uma campanha lançada por uma chave sem restrição, ou pelo painel, vai para a base inteira.