> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polymorfa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Envie uma campanha

> Crie uma campanha com destinatários, lance-a, acompanhe cada destinatário e interrompa-a, do início ao fim pela API.

Uma campanha transforma uma lista de números em mensagens do WhatsApp enviadas
em ritmo controlado e com verificações de segurança, a partir dos números de um
projeto. Este guia percorre todo o caminho pela API: construir (build) a lista de
destinatários, lançar, acompanhar o envio e interromper.

As campanhas são o único caminho de envio em massa na Polymorfa. Cada envio
passa pelas verificações de plano, vazão, opt-out, contato e aquecimento
descritas em [Campanhas e segurança dos números](/pt-br/guides/campaigns/safety).
Para comparar de duas a quatro mensagens com parte do público antes de enviar a
melhor para o restante do público, consulte [Faça um teste A/B de uma campanha](/guides/campaigns/ab-test-a-campaign).

## Escolha uma superfície

Dois caminhos públicos chegam ao mesmo mecanismo de campanhas, com as mesmas
verificações, os mesmos erros e os mesmos resultados.

| Superfície | Caminho | Credencial |
| - | - | - |
| Messaging API | `/messaging/projects/{projectSlug}/campaigns` | Credencial do projeto |
| Platform API | `/platform/campaigns` | Chave de API da equipe com `projectId`, ou um token de projeto para o próprio projeto |

A leitura exige `campaigns:read`. Criar, lançar, reagendar, pausar, retomar,
interromper e excluir exigem `campaigns:manage`. Os exemplos abaixo usam a
Messaging API; a Platform API recebe o ID da campanha no caminho. Na Platform
API, uma chave de API da equipe indica o projeto com `projectId` em toda
requisição de campanha, e um token de projeto fica vinculado ao próprio projeto
e omite esse campo.

## Crie a campanha com os destinatários

### Use o Console

No builder de campanhas, escolha um público, importe um CSV ou cole números de
telefone. O resumo da importação mostra os destinatários mantidos, os
duplicados e as linhas inválidas. Para números colados, o resumo continua
visível depois da importação, para que você corrija os números de origem antes
de continuar.

Selecione os números que vão enviar a campanha. Números Free e Standard
aparecem desativados na seleção, com uma ação de upgrade, e você pode salvar o
rascunho antes de fazer o upgrade. Cada número mostra o tipo de envio. Depois
que você seleciona um, os números de outro tipo de envio ficam desativados,
porque uma campanha envia por um único tipo. Em **Intervalo entre mensagens**,
escolha uma predefinição de **Rápido** (de 1,8 a 5 segundos entre mensagens) a
**Muito lento** (de 2 a 5 minutos), ou escolha **Personalizado** para definir
você mesmo a taxa e o atraso aleatório. A tabela de
[predefinições de intervalo](/pt-br/guides/campaigns/safety#predefinições-de-intervalo) lista os
valores da API por trás de cada predefinição. Aguarde **Rascunho salvo** antes
de lançar. Se o salvamento falhar, o builder mostra o motivo e marca as
alterações como não salvas. Uma restrição de envio também aparece na etapa de
revisão; resolva-a antes de lançar.

A etapa de revisão verifica de novo o público selecionado. Se a contagem de
destinatários mudou desde que o rascunho foi salvo, revise a nova contagem e
confirme-a antes de lançar. O builder verifica mais uma vez quando você lança;
se a contagem mudar durante a revisão, ele para e pede que você confirme o
público atualizado.

Escreva na mensagem qualquer instrução de opt-out. O builder não acrescenta um
rodapé nem envia um pedido de consentimento separado. Revise as
[configurações de palavras-chave](/pt-br/guides/campaigns/opt-outs) da sua equipe
antes de enviar.

O agendamento usa o fuso horário local do seu navegador. Confira o fuso ao lado
da data e da hora e de novo na etapa de revisão antes de agendar.

Para enviar apenas em horários definidos, ative **Enviar apenas nos horários
definidos** na etapa de agendamento. A opção começa com segunda a sexta, das
09:00 às 18:00, no fuso horário do seu navegador. A etapa de revisão e a página
da campanha mostram a janela. Consulte
[Escolha quando a campanha envia](#escolha-quando-a-campanha-envia).

### Use a API

Envie os destinatários na requisição, aponte para um
[público](/pt-br/guides/campaigns/audiences) com `recipientListId` ou faça as
duas coisas.

```bash theme={null}
curl -X POST "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: september-launch-1" \
  -d '{
    "name": "September launch",
    "templateId": "'"$TEMPLATE_ID"'",
    "recipients": [
      { "phone": "+14155550100", "variables": { "first_name": "Ada" } },
      { "phone": "+442071838750", "variables": { "first_name": "Alan" } }
    ],
    "senderConfig": {
      "sessionIds": ["'"$SESSION_ID"'"],
      "throughput": { "ratePerMinute": 20, "perNumberDailyCap": 500 }
    }
  }'
```

O campo `recipients` aceita no máximo 1.000 entradas e funciona no modo tudo ou
nada: uma entrada inválida faz a requisição ser recusada e é identificada, para
que uma campanha nunca seja criada sem alguém por omissão silenciosa. Um número
repetido na requisição é mantido uma vez. Valem as mesmas regras de número e de
variável dos membros de público.

`senderConfig.sessionIds` fixa os números que enviam a campanha. Um rascunho
pode fixar números de qualquer plano. Quando você lança, todo número fixado
precisa estar conectado e em um plano que inclua Campanhas. Uma campanha que
não fixa nenhum número usa os números conectados e sem restrições do projeto
cujos planos incluem Campanhas, com um único tipo de envio: números do WhatsApp
quando houver algum pronto, senão números da Cloud API, senão números de teste.
Para enviar de um conjunto específico, fixe esse conjunto.

Adicione mais destinatários enquanto a campanha ainda é um rascunho, 1.000 por
requisição:

```bash theme={null}
curl -X POST "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID/recipients" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: september-launch-batch-2" \
  -d '{ "recipients": [{ "phone": "+14155550101", "variables": { "first_name": "Grace" } }] }'
```

O acréscimo informa `added`, `recipientCount`, `duplicateCount`,
`invalidCount` e até 20 `invalidRows`, e ignora um número que já está na
campanha. Depois que a campanha é lançada, o acréscimo retorna `409`.

Envie um `Idempotency-Key` para que um acréscimo possa ser repetido com
segurança após um timeout ou uma resposta perdida. Depois que um acréscimo dá
certo, uma nova tentativa com a mesma chave e o mesmo corpo em até 24 horas não
é aplicada de novo: ela retorna as contagens daquele acréscimo com
`Idempotent-Replayed: true`, mesmo que a campanha tenha sido lançada nesse
meio-tempo. Um acréscimo que falhou não deixa registro, então uma nova
tentativa com a mesma chave o executa de novo. O acréscimo da Platform API
aceita o mesmo cabeçalho.

### Edite o rascunho

Envie apenas os campos que você quer mudar. Na Messaging API, `name`,
`recipientListId`, `senderConfig`, `scheduledAt`, `sendWindow`, `templateId`
e `variableMapping` podem ser atualizados; qualquer outro campo é recusado com
`400`. `templateId` e `variableMapping` só mudam enquanto a campanha é um
rascunho não lançado; depois do lançamento, um valor diferente retorna `409`.
Um `scheduledAt` não nulo precisa ser um inteiro em milissegundos Unix, de zero
até o máximo de Date do JavaScript; um valor fora desse intervalo retorna
`400`.

```bash theme={null}
curl -X PATCH "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "September launch (EU)", "senderConfig": { "sessionIds": ["'"$SESSION_ID"'"] } }'
```

A Platform API aceita as mesmas alterações em
`PATCH /platform/campaigns/{campaignId}`, além de `messagesArray` (consulte
[Envie várias mensagens](#envie-várias-mensagens)). Depois do lançamento, um
`messagesArray` diferente também retorna `409`.

Para enviar a outro público, atualize o rascunho com outro `recipientListId`.
Depois que a campanha é lançada, o público fica fixo e a atualização retorna
`409`. Mudar o público de um rascunho substitui os destinatários existentes;
depois da mudança, adicione os destinatários individuais que você ainda quiser.

Um rascunho salva qualquer seleção de números, em qualquer plano; o lançamento
a verifica. Depois do lançamento, um novo `senderConfig` precisa passar pelas
verificações de lançamento e pode retornar `402 campaigns_not_entitled` ou
`400 invalid_parameter`. Um `scheduledAt` diferente só é aceito antes do
lançamento. Para mover um início agendado que ainda não começou,
[reagende-o](#reagende-um-lançamento-agendado).

## Envie várias mensagens

Uma campanha envia um modelo aprovado ou até 10 mensagens compostas, em ordem.
Defina as mensagens compostas com `messagesArray` na requisição de criação ou
atualização de campanha da Platform API. Cada mensagem tem um texto `source` e
pode ter também `media`, `buttons` e um `footer`. `delayAfterSec` é a espera
mínima, de 0 a 86.400 segundos, depois que a mensagem anterior foi aceita. Uma
campanha com modelo envia apenas o modelo.

Um modelo precisa estar completo e, em números da Cloud API, aprovado para
todos os números de envio. Números com dispositivos vinculados e números de
teste o enviam como uma mensagem comum. Modelos de autenticação (código de uso
único) não podem ser enviados por uma campanha; o lançamento retorna
`400 invalid_parameter` em `templateId`.

```bash theme={null}
curl -X PATCH "https://api.polymorfa.com/platform/campaigns/$CAMPAIGN_ID?projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messagesArray": [
      { "version": 2, "source": "Hi {{first_name | there}}, your order shipped." },
      { "version": 2, "source": "Track it here: https://example.com/t/{{order_id}}", "delayAfterSec": 120 }
    ],
    "templateId": null
  }'
```

`"templateId": null` faz um rascunho que tinha um modelo passar a usar as
mensagens compostas; enquanto um modelo estiver definido, ele é a única
mensagem enviada.

Anexe mídia com uma `media.url` https pública, que o número de envio baixa no
momento do envio. O lançamento retorna `400 invalid_parameter`, indicando a
mensagem, quando um anexo não tem `url`.

`{{name}}` insere uma variável do destinatário; `{{name | fallback}}` usa o
fallback quando a variável está ausente ou vazia. Uma mensagem sem texto,
mídia, botão de link ou de chamada, rodapé ou formulário de fluxo não é
enviada. Em números do WhatsApp, botões de link e de chamada são enviados como
linhas de texto e as respostas rápidas ficam de fora.

Toda mensagem passa pelas mesmas verificações da primeira: ritmo, limites por
número, cota de aquecimento, indicador de digitação e histórico de contato do
destinatário. Antes de cada mensagem, a Polymorfa verifica de novo a lista de
opt-outs da equipe, então um destinatário que responde STOP não recebe mais
nada. O ritmo, os limites e uma campanha pausada podem tornar uma espera mais
longa que `delayAfterSec`, nunca mais curta. Cada mensagem seguinte é enviada
pelo número que enviou a primeira. Se esse número não puder enviar, a mensagem
aguarda até que ele possa.

### Como os resultados de várias mensagens são contados

`status`, `sentAt`, `deliveredAt`, `readAt` e `externalMessageId` do
destinatário descrevem a primeira mensagem. `sentCount`, `failedCount` e
`skippedCount` da campanha contam cada destinatário uma vez.

| O que acontece | Resultado |
| - | - |
| A primeira mensagem falha depois de todas as tentativas | O destinatário fica `failed` e nenhuma mensagem seguinte é enviada |
| Uma mensagem seguinte é enviada | `messagesSent` aumenta e um evento `campaign.recipient_sent` com aquele `messageIndex` é enviado |
| Uma mensagem seguinte falha depois de todas as tentativas | O destinatário continua `sent`. As mensagens restantes não são enviadas, `sequenceError` indica a classe da falha e `campaign.recipient_failed` traz o `messageIndex` |
| O destinatário faz opt-out entre as mensagens | As mensagens restantes não são enviadas e `sequenceError` é `opted_out` |
| A campanha é interrompida entre as mensagens | As mensagens restantes não são enviadas e `sequenceError` é `campaign_cancelled` |
| A campanha é pausada entre as mensagens | A próxima mensagem aguarda até a campanha ser retomada |

Uma mensagem seguinte que falha recebe novas tentativas conforme as
configurações de novas tentativas da campanha, como uma primeira mensagem. As
classes de falha `conversation_window_closed`, `recipient_not_on_whatsapp` e
`template_not_approved` não recebem novas tentativas, porque outra tentativa
falharia da mesma forma.

A campanha termina quando todos os destinatários foram resolvidos e nenhuma
mensagem está aguardando. `nextMessageAt` em um destinatário mostra quando a
próxima mensagem dele está prevista.

Para enviar mais uma mensagem apenas depois que a primeira foi entregue ou
lida, e nunca a quem respondeu ou fez opt-out, adicione um
[acompanhamento](/pt-br/guides/campaigns/follow-ups). Uma campanha com acompanhamento
envia uma única primeira mensagem.

> [!NOTE]
>
> Em um número da Cloud API, o WhatsApp entrega uma mensagem que não é um
> modelo aprovado somente dentro de 24 horas após a última mensagem do
> destinatário para esse número. Fora dessa janela, cada mensagem composta
> falha com `conversation_window_closed`. Uma campanha que precisa alcançar
> pessoas fora dessa janela envia um modelo aprovado.

## Preencha as variáveis de modelo e de mensagem

Cada `{{variable}}` é preenchida por destinatário. Defina `variableMapping`
para indicar uma variável do destinatário e um fallback opcional:

```bash theme={null}
curl -X PATCH "https://api.polymorfa.com/platform/campaigns/$CAMPAIGN_ID?projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "'"$TEMPLATE_ID"'",
    "variableMapping": {
      "first_name": { "source": "FirstName", "fallback": "there" },
      "coupon": { "source": "Coupon" }
    }
  }'
```

A Messaging API aceita os mesmos `templateId` e `variableMapping` quando você
cria uma campanha ou atualiza um rascunho.

Cada variável é preenchida nesta ordem:

1. O valor do destinatário para `source`, ou para a variável de mesmo nome
   quando `source` é omitido. Um valor vazio ou só com espaços conta como
   nenhum valor.
2. O `fallback` do mapeamento.
3. Um fallback escrito em uma mensagem composta como `{{first_name | there}}`.

`{{phone}}` é preenchido com o número do destinatário, a menos que o
destinatário tenha a própria variável `phone` não vazia, que é usada no lugar.
Os valores de exemplo do modelo nunca são enviados aos destinatários.

Um mapeamento tem no máximo 50 variáveis. Os nomes usam letras, dígitos, `_`,
`.` ou `-`, e um fallback tem de 1 a 1.024 caracteres. Um mapeamento inválido
retorna `400 invalid_parameter`, e `error.param` indica o campo. `templateId` e
`variableMapping` só podem mudar enquanto a campanha é um rascunho não lançado;
depois, um valor diferente retorna `409`.

No Console, escolha **Usar um modelo** na etapa **Mensagem**, selecione o
modelo e defina o campo do destinatário e o fallback de cada variável. A prévia
mostra a mensagem que um destinatário recebe.

## Varie a mensagem por peso

Para não enviar o mesmo texto a todos os destinatários, dê a uma campanha de
duas a cinco alternativas em `messageVariations`. Cada destinatário recebe uma
alternativa, escolhida por peso, e a mantém se o envio for repetido. Ao
contrário de um teste A/B, nada é comparado e nenhuma vencedora é escolhida.

A variação ponderada está em beta. Um proprietário ou administrador da equipe
inscreve a equipe na página **Campanhas** do Console. Até que a equipe esteja
inscrita e o beta seja oferecido a ela, definir `messageVariations`, lançar uma
campanha que as tenha e retomar uma campanha assim retornam
`403 feature_unavailable`.

```bash theme={null}
curl -X PATCH "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "messageVariations": [
      { "key": "hello", "weight": 60, "blueprint": { "version": 2, "source": "Hello {{first_name | there}}, your order is ready." } },
      { "key": "hi", "weight": 40, "blueprint": { "version": 2, "source": "Hi {{first_name | there}}! Your order is ready to collect." } }
    ]
  }'
```

* `key` é única na campanha: uma letra minúscula, seguida de até 15 letras
  minúsculas, dígitos, `_` ou `-`. `weight` é um inteiro de 1 a 99, e os pesos
  somam 100. `source` tem de 1 a 4.096 caracteres.
* Todas as alternativas precisam usar os mesmos marcadores `{{variable}}` e os
  mesmos fallbacks em linha, para que os mesmos destinatários se qualifiquem
  qualquer que seja a alternativa recebida.
* Uma campanha com alternativas envia apenas as alternativas. Ela não pode ter
  também mensagens compostas, um modelo ou variantes A/B.
* As alternativas são enviadas apenas de números do WhatsApp vinculados.
  Selecione-os com `senderConfig.sessionIds`. Uma campanha cujos números passam
  a ser números oficiais (Cloud API) é pausada antes do próximo envio.
* As alternativas só mudam enquanto a campanha é um rascunho não lançado. Envie
  `null` para removê-las. Depois do lançamento, uma atualização retorna `409`.
* Envios de teste de um rascunho com alternativas retornam
  `409 state_conflict`.

Se a equipe sair do beta, uma campanha em andamento com alternativas é pausada
antes do próximo envio e ainda pode ser interrompida. Retomá-la exige o beta de
novo.

## Envie um teste

Antes de lançar, envie uma cópia do conteúdo de um rascunho para o seu próprio
telefone. Um teste passa pelas mesmas verificações de renderização, envio e
segurança da campanha lançada, a partir do número que você escolher. Ele envia
todas as mensagens do rascunho em ordem, sem os intervalos entre elas, e para
na primeira mensagem que não for enviada. Ele renderiza o conteúdo com os
valores de exemplo selecionados para o teste, então a mensagem de cada
destinatário pode ser diferente onde os valores dele forem diferentes. A chave
de API precisa de `campaigns:manage` e `campaigns:read`, porque o teste pode
usar valores de exemplo armazenados dos destinatários.

* **Destinatário do teste.** `to` precisa ser o telefone de um número conectado
  à sua equipe, em qualquer projeto. Vincule o seu próprio telefone a um
  projeto para receber testes nele. Um destinatário do teste que está na lista
  de opt-outs da equipe é recusado.
* **Número de envio.** `sessionId` precisa estar conectado e em um plano que
  inclua Campanhas, o mesmo requisito do lançamento. Quando a campanha seleciona
  números com `senderConfig.sessionIds`, teste a partir de um deles.
* **Variáveis.** Sem `sampleRecipientId` nem `variables`, o primeiro
  destinatário da campanha preenche as variáveis, ou o primeiro membro do
  público quando o rascunho não tem destinatários próprios. Indique um
  destinatário com `sampleRecipientId` e substitua valores específicos com
  `variables`. O teste aplica o mapeamento de variáveis e os fallbacks da
  campanha a todas as mensagens do rascunho.
* **Estatísticas.** Um teste não é um destinatário da campanha. Ele não altera
  as contagens nem as análises da campanha e não envia webhooks de campanha.
* **Limites.** Uma equipe pode enviar 20 testes por hora, e no máximo 5 por
  hora para um mesmo destinatário do teste.

```bash theme={null}
curl -X POST "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID/test-send" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: september-launch-test-1" \
  -d '{
    "sessionId": "'"$SESSION_ID"'",
    "to": "+14155550199",
    "variables": { "first_name": "Ada" }
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": "01924f1e-7c3a-7b1d-9e2f-3a4b5c6d7e8f",
    "campaignId": "01924f1e-6a2b-7c3d-8e4f-5a6b7c8d9e0f",
    "status": "sent",
    "from": { "sessionId": "01924f1e-5b6c-7d8e-9f0a-1b2c3d4e5f6a", "phone": "+14155550123" },
    "to": "+14155550199",
    "messageId": "3EB0C767D26A1D2E5F7A",
    "variables": { "first_name": "Ada" },
    "createdAt": 1758326300000
  }
}
```

A resposta chega quando o envio termina. Um teste recusado não envia nada, a
menos que a tabela diga o contrário:

| Status e código | Causa | O que fazer |
| - | - | - |
| `400 invalid_parameter` | `to` não é um número conectado à sua equipe, ou `sessionId` não é um número pelo qual esta campanha pode enviar; `error.param` indica o campo | Escolha um destinatário do teste ou um número de envio que atenda às regras acima |
| `402 campaigns_not_entitled` | O plano do número de envio não inclui Campanhas | Mude o número para Pro ou teste a partir de um número Pro |
| `409 bansafe_cold_held` | O destinatário do teste nunca escreveu para o número de envio, e a campanha retém primeiras mensagens | Envie qualquer mensagem do telefone do destinatário do teste para o número de envio e teste de novo |
| `409 state_conflict` | A campanha não é um rascunho, tem variantes A/B ou `messageVariations`, o destinatário do teste fez opt-out, ou o número de envio é um número Hybrid Link | Teste antes do lançamento ou escolha outro destinatário do teste ou outro número. Campanhas com variantes A/B ou mensagens alternativas ainda não aceitam envio de teste |
| `422 campaign_variables_missing` | Uma variável do conteúdo selecionado não tem valor de exemplo, valor explícito nem fallback | Informe o valor ausente ou um fallback da campanha antes de testar |
| `429 rate_limit_exceeded` | Um limite de envio de teste foi atingido (`Polymorfa-RateLimit-Reason: campaign_test_send`), ou o limite de envios de campanha do número foi atingido | Aguarde `Retry-After` |
| `429 bansafe_throttled`, `429 bansafe_daily_allowance_reached` | O número de envio está com o ritmo limitado ou usou a cota de aquecimento | Aguarde `Retry-After` |
| `503 session_not_ready` | O número de envio não está conectado | Reconecte-o e teste de novo |
| `504 result_unknown` | O resultado é desconhecido; o teste ainda pode chegar | Confira o telefone do destinatário do teste antes de enviar outro teste. Uma nova tentativa com o mesmo `Idempotency-Key` não envia de novo |

Na Platform API, envie o mesmo corpo para
`POST /platform/campaigns/{campaignId}/test-send`, com `projectId` quando usar
uma chave de API da equipe. No Console, escolha **Enviar teste** na etapa de
revisão do builder ou na página de um rascunho.

## Escolha quando a campanha envia

Por padrão, uma campanha envia em qualquer horário do dia. Defina `sendWindow`
na criação, ou em uma atualização pela Messaging API ou pela Platform API, para
enviar apenas nos dias da semana e nos horários locais escolhidos:

```json theme={null}
{
  "sendWindow": {
    "timeZone": "America/Sao_Paulo",
    "days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
    "hours": [
      { "start": "09:00", "end": "12:00" },
      { "start": "14:00", "end": "18:00" }
    ]
  }
}
```

| Campo | Regra |
| - | - |
| `timeZone` | Nome de fuso horário IANA, como `Europe/Lisbon` ou `UTC`. Se você omitir, a Polymorfa armazena o fuso horário da sua equipe, ou `UTC` quando a equipe não tem um. A campanha sempre retorna o fuso armazenado. |
| `days` | De um a sete valores entre `monday` e `sunday`, sem repetição. |
| `hours` | De um a quatro intervalos. `start` é `HH:MM` no formato de 24 horas e está incluído; `end` é `HH:MM` ou `24:00` e está excluído. Os intervalos não podem se sobrepor nem passar da meia-noite. Para 20:00 a 02:00, use dois intervalos, `20:00`–`24:00` e `00:00`–`02:00`; cada intervalo vale em todos os dias de `days`. |
| `recipientTimeZone` | `true` aplica a janela no horário local de cada destinatário. O padrão é `false`. |
| `timeZoneVariable` | Variável do destinatário que contém o fuso horário IANA dele. O padrão é `timeZone`. |

Um destinatário cuja vez chega fora da janela não falha nem é ignorado. O
destinatário continua `queued` e é enviado depois que a janela abrir de novo,
no ritmo normal da campanha. A campanha continua `running`, as contagens não
mudam e nenhum evento de destinatário é enviado durante a espera. Uma campanha
cuja janela está fechada para todos não envia nada até a janela abrir. As
mudanças de horário de verão seguem as regras do fuso horário.

Com `recipientTimeZone` definido como `true`, o fuso de cada destinatário é:

1. o fuso na variável do destinatário indicada por `timeZoneVariable`, quando
   for um nome IANA válido;
2. senão, o fuso do país do número de telefone, quando o país inteiro usa um
   único deslocamento de UTC. Por exemplo, números `+44` usam `Europe/London`.
   Países com vários deslocamentos, como Estados Unidos, Brasil e México, não
   são resolvidos dessa forma;
3. senão, o `timeZone` da janela.

Os fusos dos destinatários são resolvidos quando a campanha é lançada. Para
definir um fuso você mesmo, adicione uma variável com o nome de
`timeZoneVariable` a cada membro do público ou destinatário da requisição, por
exemplo `"variables": { "timeZone": "Asia/Tokyo" }`. Se essa variável tiver
mais de 5.000 valores distintos não vazios, o lançamento retorna
`400 invalid_parameter` com `error.param` definido como
`sendWindow.timeZoneVariable`. Corrija os valores do público antes de lançar.

Uma janela inválida retorna `400 invalid_parameter`, e `error.param` indica o
campo, como `sendWindow.hours[1]`. Envie `"sendWindow": null` para remover a
janela. Você pode mudar ou remover a janela enquanto a campanha é um rascunho
ou está pausada. Uma campanha em andamento retorna `409 state_conflict`: pause-a,
atualize a janela e retome-a. Se uma campanha pausada ainda tiver envios em
andamento, a edição retorna `409 state_conflict`; tente novamente depois que
esses envios forem concluídos. Um envio travado que não chegou ao provedor pode
ser recuperado em uma tentativa de edição posterior. Se a entrega pode ter
começado, a edição retorna `409 state_conflict` enquanto esse envio está em
andamento. Tente novamente depois que ele for concluído. Se o resultado dele se
perder, o destinatário acaba se tornando um envio com falha e resultado
desconhecido; a campanha não o envia de novo. A nova janela vale para os
destinatários restantes.

## Lance a campanha

```bash theme={null}
curl -X POST "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID/launch" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: september-launch-start-1" \
  -d '{ "scheduledAt": 1758326400000 }'
```

O envio começa em `scheduledAt`, ou imediatamente quando nenhum horário de
início é definido. No lançamento, a Polymorfa copia o público para a campanha,
ignora os números na [lista de opt-outs](/pt-br/guides/campaigns/opt-outs) da equipe
e define `recipientCount` a partir dos destinatários que existem.

Em uma campanha agendada, esse retrato é feito quando a requisição de
lançamento é aceita. Adições, remoções ou alterações posteriores no público de
origem não mudam os destinatários dessa campanha. Os opt-outs são verificados
de novo antes do envio.

O lançamento verifica a seleção de números salva na campanha em relação aos
planos dos números naquele momento, então um número que mudou de plano depois
que o rascunho foi salvo é verificado no novo plano. Todo número fixado precisa
estar conectado e em um plano que inclua Campanhas, e todos os números fixados
precisam usar um único tipo de envio:

| Status e código | Causa |
| - | - |
| `403 bansafe_org_suspended` | A equipe está suspensa |
| `402 campaigns_not_entitled` | O plano de um número fixado não inclui Campanhas, um número fixado não está no projeto, ou uma campanha que não fixa números não tem no projeto nenhum número em um plano que inclua Campanhas |
| `400 invalid_parameter` | Um número fixado está desconectado, não tem telefone ou usa um tipo de envio diferente de outro número selecionado; `error.param` é `senderConfig.sessionIds` |
| `400 campaign_throughput_capped` | Uma vazão solicitada está acima do teto de segurança; `error.param` indica o campo |
| `400 invalid_parameter` | O modelo está incompleto; `error.param` é `templateId` |
| `400 template_not_approved` | O WhatsApp não aprovou o modelo em um número da Cloud API pelo qual a campanha pode enviar |
| `422 campaign_variables_missing` | Alguns destinatários não têm valor nem fallback para uma variável; a mensagem indica as variáveis e conta os destinatários |
| `409 campaign_approval_required` | Sua equipe exige aprovação para lançamentos acima de um limite de destinatários, e este lançamento enviaria para mais destinatários. Nada foi enviado. Consulte [Exija aprovação para envios grandes](#exija-aprovação-para-envios-grandes) |
| `409 state_conflict` | A campanha não é um rascunho, já foi lançada e aguarda o horário de início, ou os números de envio, o plano ou a conexão de um número selecionado mudaram enquanto o lançamento era verificado. Para mover um início agendado, reagende-o. |
| `503 service_unavailable` | Não foi possível verificar os planos dos números; lance de novo |

Para enviar a todos, adicione fallbacks ou mapeie outra variável do
destinatário e lance de novo. Para lançar sem esses destinatários, envie
`"skipMissingVariables": true` no corpo do lançamento. Eles são marcados como
`skipped`, com `lastError` `missing_variable`, antes de a primeira mensagem ser
enviada, e contados em `skippedCount`. Cada mensagem é verificada de novo pelas
mesmas regras quando é enviada, então nenhum destinatário recebe uma mensagem
com uma variável vazia; um destinatário ignorado nesse momento emite
`campaign.recipient_skipped` com `reason` `missing_variable`.

Criar, lançar, reagendar, pausar, retomar, interromper e enviar teste aceitam o
cabeçalho `Idempotency-Key` na Messaging API; a Platform API o aceita em
lançar, reagendar, pausar, retomar, interromper e enviar teste. Uma chave
repetida não inicia um segundo lançamento nem um segundo envio de teste. Uma
nova tentativa depois que a primeira requisição deu certo retorna
`409 idempotency_completed` em vez da resposta original. Uma nova tentativa
enquanto a primeira requisição ainda está em execução retorna
`409 idempotency_in_progress`, e uma chave reutilizada com uma requisição
diferente retorna `409 idempotency_conflict`. Consulte
[Erros](/api/errors#idempotency-completed).

## Exija aprovação para envios grandes

Um proprietário ou administrador da equipe pode exigir aprovação para qualquer
lançamento que enviaria para mais destinatários do que um limite. No Console,
defina isso em **Campanhas > Configurações**. Pela API:

```bash theme={null}
curl -X GET "https://api.polymorfa.com/platform/campaign-settings" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

```json theme={null}
{ "launchApproval": { "recipientThreshold": 5000 } }
```

Apenas um proprietário ou administrador da equipe pode mudar o limite, no
Console; um valor vazio desativa a aprovação. Não há API para mudá-lo, então
uma credencial não consegue desativar a aprovação que retém os próprios
lançamentos.

Quando o limite está definido, um lançamento feito por uma chave de API, um
token de projeto ou um membro da equipe que não é proprietário nem
administrador conta os destinatários para os quais o lançamento enviaria,
depois dos opt-outs. Se forem mais do que o limite, nada é enviado: a campanha
continua como rascunho e o lançamento responde
`409 campaign_approval_required`. A mensagem de erro indica a solicitação de
aprovação que foi registrada. Lançar de novo com a campanha inalterada retorna
a mesma solicitação. Um lançamento feito por um proprietário ou administrador
conectado ao Console não precisa de aprovação.

Liste as solicitações de uma campanha, das mais recentes para as mais antigas:

```bash theme={null}
curl "https://api.polymorfa.com/platform/campaigns/$CAMPAIGN_ID/approvals?projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

Cada solicitação indica a `action` retida (`launch`, `resume` ou `reschedule`)
e os `reasons`: `audience_threshold`, `agent_request` ou ambos. Cada
solicitação tem um `status`:

| Status | Significado |
| - | - |
| `pending` | Aguardando um proprietário ou administrador |
| `approved` | Aprovada; a ação foi executada e `operationId` é a operação dela |
| `rejected` | Rejeitada; a ação não foi executada e nada foi enviado |
| `superseded` | Uma solicitação posterior, ou uma alteração na campanha, a substituiu |
| `expired` | Não foi decidida em 72 horas |

Um lançamento, uma retomada ou um novo horário de início solicitado por um
agente de IA pelo servidor MCP hospedado sempre registra uma solicitação com o
motivo `agent_request`, qualquer que seja o limite. Consulte
[Servidor MCP](/pt-br/integrations/mcp-server#uma-pessoa-aprova-os-envios-de-campanhas).

Um proprietário ou administrador aprova ou rejeita uma solicitação na página da
campanha no Console. Aprovar lança a campanha na mesma etapa, com todas as
verificações de lançamento, o horário de início solicitado e os destinatários
contados para a solicitação. Se o conteúdo, os números de envio, o agendamento,
o modelo ou os destinatários da campanha mudaram depois da solicitação, nada é
enviado e a resposta é `409 campaign_approval_stale`; lance de novo para
solicitar uma nova aprovação. Uma solicitação pode ser aprovada uma vez. Um
horário de início solicitado que já passou inicia a campanha quando ela é
aprovada.

## Reagende um lançamento agendado

Uma campanha lançada aguarda o `scheduledAt` antes de enviar qualquer coisa.
Até lá, você pode antecipar ou adiar esse início, ou começar a enviar agora,
sem interromper e lançar a campanha de novo:

```bash theme={null}
curl -X POST "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID/reschedule" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: september-launch-move-1" \
  -d '{ "scheduledAt": 1758412800000 }'
```

Na Platform API, envie o mesmo corpo para
`POST /platform/campaigns/{campaignId}/reschedule`, com `projectId` no corpo
quando usar uma chave de API da equipe.

`scheduledAt` é obrigatório. Defina-o como um horário Unix em milissegundos,
ou como `null` para começar a enviar agora. Um horário que não está no futuro
também inicia a campanha agora. A resposta é a campanha com o novo
`scheduledAt` e o `operationId` da execução de entrega. Enviar de novo o
horário de início atual não muda nada.

Reagendar muda apenas quando a campanha começa. Os destinatários e as
mensagens continuam como estavam quando você a lançou. Cada envio ainda é
verificado quando a campanha começa, da mesma forma que um início que nunca foi
movido. Cada alteração aceita é registrada como uma entrada `rescheduled` no
histórico da campanha e envia um evento
[`campaign.rescheduled`](/api/webhooks#campaign-events).

| Status e código | Causa |
| - | - |
| `400 invalid_parameter` | `scheduledAt` está ausente, ou não é um número inteiro positivo nem `null` |
| `403 bansafe_org_suspended` | A equipe está suspensa. Você ainda pode interromper a campanha. |
| `409 state_conflict` | A campanha já começou, terminou ou está sendo interrompida, ou nunca foi lançada. Em vez disso, lance um rascunho com `scheduledAt`. |

Depois que uma campanha é lançada, uma atualização não muda o `scheduledAt`.
Uma atualização que envia um `scheduledAt` diferente retorna `409`; em vez
disso, reagende a campanha.

No Console, abra uma campanha agendada e escolha **Reagendar**. Escolha uma
nova data e hora no fuso horário do seu navegador, ou escolha **Iniciar
agora**.

## Acompanhe o envio

Liste os destinatários na ordem da fila, com paginação por cursor. `limit` vai
de 1 a 100, com padrão 25.

```bash theme={null}
curl "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID/recipients?status=skipped&reason=opted_out&limit=100" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "0c0e4f2a-2b1d-4a67-9bb2-1f7f4a2e91a3",
      "phone": "+14155550100",
      "variables": { "first_name": "Ada" },
      "variantKey": null,
      "status": "skipped",
      "attempts": 0,
      "lastError": "opted_out",
      "failureReason": "opted_out",
      "externalMessageId": null,
      "queuedAt": 1758326400000,
      "sentAt": null,
      "deliveredAt": null,
      "readAt": null,
      "failedAt": 1758326402000,
      "respondedAt": null,
      "messagesSent": 0,
      "nextMessageAt": null,
      "sequenceError": null
    }
  ],
  "page": { "nextCursor": "cjE6MjU", "hasMore": true }
}
```

Siga `page.nextCursor` até `page.hasMore` ser `false`. Filtre com `status`
(`queued`, `sending`, `sent`, `delivered`, `read`, `failed` ou `skipped`), com
`reason` ou com os dois.
`messagesSent`, `nextMessageAt` e `sequenceError` informam o progresso de uma
[campanha com várias mensagens](#envie-várias-mensagens).

### Motivos de falha

`failureReason` é o motivo estável pelo qual um destinatário não recebeu o
envio, ou pelo qual a última tentativa dele não deu certo. É `null` quando nada
deu errado. Nunca contém o conteúdo da mensagem, o número do destinatário nem
um erro bruto. Filtre por ele com `reason`.

| `failureReason` | Significado |
| - | - |
| `opted_out` | O número está na lista de opt-outs da equipe |
| `cold_held` | O número nunca escreveu para o número de envio, e a campanha retém envios frios |
| `campaign_cancelled` | A campanha foi interrompida antes de este destinatário receber o envio |
| `invalid_recipient` | O número não é um endereço válido do WhatsApp |
| `session_not_connected` | O número de envio não estava conectado |
| `ack_timeout` | O WhatsApp não confirmou o envio a tempo |
| `blocked_by_safety` | Uma verificação de segurança recusou o envio antes da tentativa; o destinatário aguarda a campanha ser retomada |
| `send_failed` | O WhatsApp recusou o envio, ou não foi possível confirmar o resultado |
| `missing_variable` | O destinatário não tinha valor nem fallback para uma variável: ignorado no lançamento com `skipMissingVariables`, ou quando uma mensagem foi verificada no momento do envio |
| `frequency_cap` | O destinatário atingiu o limite de frequência de contato móvel da equipe ou da campanha |
| `other` | Qualquer outro motivo |

Novos códigos podem ser adicionados, então trate um valor desconhecido como
`other`. O campo legado `lastError` retorna o mesmo código normalizado de
`failureReason`; erros brutos armazenados não são retornados.

### Exporte os destinatários como CSV

`GET .../recipients/export` retorna CSV na ordem da fila. Exportações sem
filtro retornam até 1.000 linhas por requisição (`limit`, padrão 1.000). Cada
página começa com uma linha de cabeçalho. Quando restam mais linhas, envie o
cabeçalho de resposta `Polymorfa-Next-Cursor` como `cursor` para obter a
próxima página.

Os filtros de status e de motivo de falha retornam uma página de até 1.000
destinatários correspondentes. Exportações filtradas não podem usar cursor, e
`limit`, se enviado, precisa ser `1000`; qualquer outro valor retorna `400`. Se
mais de 1.000 destinatários corresponderem, a API retorna `409` sem um arquivo
parcial. Para resultados maiores, exporte todos os destinatários e filtre o CSV
localmente.

```bash theme={null}
curl -D headers.txt -o failed.csv \
  "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID/recipients/export?status=failed" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

```csv theme={null}
recipientId,phone,status,failureReason,attempts,variantKey,queuedAt,sentAt,deliveredAt,readAt,failedAt,respondedAt
0c0e4f2a-2b1d-4a67-9bb2-1f7f4a2e91a3,'+14155550100,failed,invalid_recipient,3,,2026-09-20T00:00:00.000Z,,,,2026-09-20T00:04:12.000Z,
```

Os horários estão em ISO 8601 em UTC, e campos vazios são nulos. Um apóstrofo
no início mantém os números de telefone como texto em planilhas; remova-o ao
ler células de telefone no código. A exportação nunca contém conteúdo de
mensagens nem variáveis de modelo. Uma página sem filtro reflete o resultado de
cada destinatário no momento da requisição. Um arquivo filtrado reflete uma
única leitura do banco de dados, então mudanças de status não causam omissões
entre páginas filtradas. A Platform API oferece a mesma exportação em
`GET /platform/campaigns/{campaignId}/recipients/export?projectId=...`.

No Console, o painel **Destinatários** da campanha filtra por status e motivo,
e **Baixar CSV** salva até 1.000 destinatários filtrados pela mesma exportação.
Para resultados filtrados maiores, limpe os filtros, baixe todos os
destinatários e filtre o arquivo localmente.

> [!WARNING]
>
> `GET /platform/campaigns/{campaignId}/recipients` retorna uma página e um
> objeto `page`. Se a sua integração espera o array sem paginação anterior,
> atualize-a para seguir `page.nextCursor` até ler todos os destinatários.

As análises da campanha informam o funil e o tempo de resposta:

```bash theme={null}
curl "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID/analytics" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

| Campo | Significado |
| - | - |
| `recipientCount` | Destinatários na campanha |
| `sentCount` | Mensagens aceitas pelo WhatsApp |
| `deliveredCount` | Destinatários cujo aparelho confirmou a entrega |
| `readCount` | Destinatários que abriram a mensagem; uma leitura implica uma entrega |
| `failedCount` | Envios que o WhatsApp recusou |
| `skippedCount` | Destinatários que nunca receberam uma tentativa, como por opt-out ou retenção de envio frio |
| `respondedCount` | Destinatários que responderam |
| `responseRate` | `respondedCount` dividido por `sentCount`, e `0` antes de qualquer envio |
| `averageResponseTimeMs` | Tempo médio do envio até a primeira resposta, e `null` antes da primeira resposta |
| `minResponseTimeMs`, `maxResponseTimeMs` | Primeira resposta mais rápida e mais lenta |

Uma resposta conta uma vez por destinatário. Ela é atribuída ao destinatário
mais recente para o qual o número que respondeu recebeu um envio, no mesmo
número de envio, em até 7 dias depois desse envio.

Para contar quem abre um link na mensagem, consulte
[Rastreie links de campanha](/pt-br/guides/campaigns/link-tracking).

Inscreva um webhook de projeto em `campaign.recipient_delivered`,
`campaign.recipient_read` e `campaign.recipient_replied` para acompanhar cada
destinatário depois do envio, quando sua equipe estiver inscrita no beta de
eventos de engajamento de destinatários e o lançamento gradual dele estiver
ativado. As cargas do webhook incluem os IDs da campanha e do destinatário, o
número de envio e o ID da mensagem original, quando disponível. O evento de
resposta inclui o tempo decorrido, mas não o corpo da mensagem. Se sua equipe
tem acesso ao beta do stream de eventos com credencial de servidor, as entradas
dele expõem o tipo e o horário do evento sem esses campos do destinatário. Use
a API de Campanhas para obter o status do destinatário a partir de uma
notificação do stream. Tokens de cliente não podem abrir esse stream.

A Platform API também lista o histórico do ciclo de vida da campanha, do mais
recente para o mais antigo:

```bash theme={null}
curl "https://api.polymorfa.com/platform/campaigns/$CAMPAIGN_ID/events?projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

Cada entrada traz `kind` (`launched`, `rescheduled`, `paused`, `resumed`,
`cancelled`, `completed` ou `failed`) e o horário em que aconteceu. Uma entrada
`rescheduled` tem `payload.previousScheduledAt` e `payload.scheduledAt`. Uma
entrada `failed` tem `payload.reason: "all_recipients_failed"`. A campanha
falha quando todos os destinatários terminam como falha. Se algum destinatário
recebeu o envio ou foi ignorado, a campanha é concluída e as contagens mostram
esses resultados.

## Pause, retome e interrompa

```bash theme={null}
curl -X POST "https://api.polymorfa.com/messaging/projects/$PROJECT_SLUG/campaigns/$CAMPAIGN_ID/pause" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

Pausar retém uma campanha em andamento; os envios já em andamento terminam.
Retomar envia aos destinatários restantes depois das mesmas verificações de
antes da pausa, incluindo a [janela de envio](#escolha-quando-a-campanha-envia).

Interromper cancela. Os destinatários que ainda não receberam o envio não o
recebem: cada destinatário na fila passa a ser `skipped` com `failureReason`
`campaign_cancelled` e é contado uma vez em `skippedCount`. A campanha passa
para `cancelling` e depois para `cancelled` quando os envios já em andamento
terminam. Uma campanha sem execução de entrega ativa fica `cancelled` na hora,
e `operationId` é `null`. A interrupção nunca é bloqueada por suspensão nem
pela perda do direito do plano, então uma campanha restrita sempre pode ser
interrompida.

No Console, **Interrupção solicitada** confirma que sua solicitação foi aceita.
A página de detalhes é atualizada até a campanha chegar a **Cancelada** e
mostra os resultados finais dos destinatários. Os envios já em andamento podem
terminar durante essa transição.

Uma campanha sem execução de entrega ativa não pode ser pausada nem retomada.
Interrompa-a, duplique-a e lance a cópia.

## Arquive ou exclua

Arquive uma campanha `completed`, `failed` ou `cancelled` quando quiser manter
o histórico dela e marcá-la como arquivada. Uma campanha que ainda está
iniciando, em andamento, pausada ou sendo interrompida não pode ser arquivada.

Excluir remove uma campanha `draft`, `completed`, `failed`, `cancelled` ou
`archived`. Qualquer outra campanha, e um rascunho que ainda tem um lançamento
agendado, é recusada com `409`: interrompa-a primeiro e depois exclua-a. Se uma
campanha concluída ou com falha ainda tiver um evento final a entregar, excluir
ou arquivar retorna `409`. Arquivar e excluir também aguardam o fim da execução
de entrega. Tente novamente depois que o evento e a execução terminarem.

```bash theme={null}
curl -X DELETE "https://api.polymorfa.com/platform/campaigns/$CAMPAIGN_ID?projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $POLYMORFA_KEY"
```

## Reenvie o que falhou

`POST .../requeue` devolve os destinatários com falha para `queued`. Defina
`includeSkippedError` como `true` para devolver à fila também os destinatários
ignorados. Um número na lista de opt-outs da equipe nunca é reservado para
envio, qualquer que seja o status do destinatário.

Se a entrega não puder começar, o destinatário continua na fila para uma nova
tentativa automática. Se um envio pode ter chegado ao WhatsApp, mas o resultado
não pode ser confirmado, o destinatário não recebe o envio de novo
automaticamente.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.