Skip to main content

Mensagens de Template

Quando você precisa escrever primeiro para um cliente — recuperar um carrinho abandonado, avisar que um pedido está pronto, entregar um PIN de retirada — a mensagem tem que sair como um template aprovado pela Meta. Estes endpoints são a porta pública para pedir esse envio e para consultar onde ele parou.
Sua conta precisa estar habilitada antes da primeira chamada. O contrato é publicado com antecedência para que você possa construir; a habilitação é por conta e nós a combinamos com você junto com o template e o número remetente.Enquanto sua conta não estiver habilitada, cada chamada responde 422 com "type": "account_not_eligible" — sempre, e não de forma intermitente. Não é problema da sua requisição e nenhuma retentativa muda isso: é configuração do nosso lado, e a resposta diz isso com essas palavras em vez de esconder atrás de um erro genérico. Fale conosco para habilitá-la. Qualquer mudança no contrato é anunciada no Changelog.

O que faz e o que não faz

Pré-requisitos

Para saber quais templates você pode enviar, consulte o catálogo. GET /v1/templates lista os que você tem aprovados, com o nome e o idioma exatos que esta chamada espera. Uma lista vazia significa que nenhum foi aprovado ainda, e até que haja um cada envio é recusado. O campo sendable de cada template diz se esta API consegue enviá-lo: é false quando o texto mistura variáveis por posição e por nome, numera com buracos ou tem variável no cabeçalho (esta API preenche só o corpo). Um template com sendable: false responde 422 ao enviar, e o ajuste é na Meta, não no seu request.

Enviar uma mensagem

Campos do request

O identificador da sua empresa não viaja no request: ele é derivado da sua API key (Autenticação). O template viaja sim, por nome e idioma, e é validado contra os que você tem aprovados.

Respostas

Envio aceito — 202

Validamos, reservamos o event_id e entregamos ao serviço de envio. A resposta chega assim que isso acontece e não traz o desfecho:
accepted significa “a caminho”, não “deu certo”. O envio segue seu curso depois da resposta. Não leia isso como sucesso — consulte o endpoint de estado, que é onde ele se resolve contra a mensagem real. É o único caminho: os webhooks de /v1/webhooks emitem apenas eventos de pedido (order.created, order.status_changed), nenhum de entrega.
Configure o timeout do seu cliente em 35 segundos ou mais. O POST aguarda a chamada ao serviço de envio, que tem o seu próprio teto de 30 segundos. No caminho normal ele responde em menos de um segundo, mas esse é o caso típico e não uma garantia: um cliente com timeout de 5 ou 10 segundos abandona requests saudáveis que ainda estão em voo, e depois reporta falhas que nunca aconteceram.

Sem envio novo — 200

Um 200 significa que esta requisição não produziu um envio. São dois casos:
  • Um event_id repetido: devolve o estado guardado da primeira requisição, como descrito na seção de idempotência abaixo.
  • Um envio inviável de saída — o aviso está vencido, a conversa do cliente está em pausa, ou o comerciante desligou o envio pelo painel: responde "status": "discarded" com o motivo em status_detail (stale, paused ou disabled). A requisição funcionou; o envio está desligado de propósito.

Idempotência

Mesmo event_id = mesmo envio. Reenviá-lo devolve o estado da primeira requisição e nunca gera um segundo WhatsApp. Por isso é seguro repetir depois de um timeout de rede. A idempotência roda antes das validações de telefone, template e frescura: um reenvio devolve o estado guardado sem julgar esses campos de novo. O que roda primeiro é a forma do corpo — o charset do event_id, os tamanhos de template.name e template.language, o parse de occurred_at. Um reenvio malformado recebe 422 e nunca chega à idempotência. Há um caso em que um event_id deixa de servir: se do lado do envio ele já ficou atrelado a outra mensagem, ou saiu da janela de 30 dias, a resposta é 409 com "type": "idempotency_conflict". Mande esse evento com um event_id novo — repetir o mesmo devolve o mesmo 409. De propósito não guardamos estado sob a chave recusada: se guardássemos, a próxima tentativa receberia um reenvio silencioso em vez do 409 que diz o que corrigir.
Um 422 num reenvio merece uma olhada antes de descartar o evento. Se a primeira tentativa pode já ter saído, o evento não está perdido: corrija a forma do corpo e reenvie com o mesmo event_id, que continua seguro e devolve o estado real. Quando o 422 foi de conta, esse reenvio responde 200 com "status": "failed" — é o estado guardado da primeira tentativa, não um envio novo.
O event_id não é um identificador de rastreio. Se o seu sistema gera um id novo a cada tentativa HTTP, esse valor não serve como event_id: cada retentativa apareceria como um evento distinto e o cliente receberia o WhatsApp mais de uma vez. Para rastrear tentativas use o header X-Request-ID — veja Request IDs.

Consultar o estado

“Enviado” não é “entregue”: a Meta reporta a entrega ao telefone minutos ou horas depois — ou nunca, se o telefone estiver desligado. Esse ciclo é consultado pelo seu event_id:
Um event_id que nunca foi enviado responde 404; um que não respeita o charset responde 422.
status_detail em um failed não é uma lista fechada e pode estar ausente. Pode ser uma recusa de conta (account_not_eligible, no_sender_number), um envio que não chegou a se completar (send_failed) ou que foi cancelado antes de ser tentado (send_cancelled), ou, quando o provedor recusa a entrega com um código, provider_error_<código> — por exemplo provider_error_131049. O código é o da Meta e o catálogo deles muda sem nos avisar, então não ramifique de forma exaustiva sobre este campo: decida pelo status, registre status_detail e trate qualquer valor desconhecido — ou ausente — como uma falha genérica.Um accepted também pode trazer delivery_not_observable: o envio desse comerciante vive em uma instância própria que este deployment não consegue ler, então o status não vai se resolver a cada consulta — não é uma falha.
Dois detalhes úteis na integração:
  • updated_at é quando o registro mudou pela última vez, não quando você consultou. Consultar não o move, mas uma escrita de entrega que deixa o status igual move, então não o use como marca d’água: reconcilie pelo event_id.
  • Só failed e discarded são definitivos. sent, delivered e read são resolvidos de novo a cada consulta, porque ainda pode chegar um recibo posterior — inclusive um de falha depois de um read.

Janelas de tempo

Envie o offset em occurred_at. Um valor sem fuso horário é lido como UTC. Se o seu sistema envia hora local do Peru (UTC-5), o evento envelhece 5 horas a mais contra uma janela de 6: um carrinho abandonado há duas horas computa como de sete horas e volta discarded com motivo stale — respondendo 200, sem erro e sem mensagem enviada.

Erros

Os erros seguem o formato padrão da API:
Os dois 422 de conta se distinguem por detail[0].type, não pelo campo: eles chegam com "loc": ["body"] porque não há um valor a corrigir. É o mesmo formato de array de qualquer outro 422 — veja Tratamento de Erros. Já o 409 usa o formato padrão e traz o seu type na raiz.
Um 502 não significa mais “pode ser qualquer coisa”. As recusas que dependem da sua conta ou do seu evento têm resposta própria: conta não habilitada e falta de número remetente são 422, uma conversa em pausa é 200 com "status": "discarded" e "status_detail": "paused", e uma chave repetida é 409. O que sobra no 502 é uma falha real do serviço interno de envio. Repita com backoff algumas poucas vezes; se persistir, pare de repetir e fale conosco.
Nem um aviso vencido, nem uma conversa em pausa, nem o envio desligado são erros: os três respondem 200 com "status": "discarded" e "status_detail" em "stale", "paused" ou "disabled". Uma pausa é posta por um agente humano que assumiu a conversa, ou por um corte de faturamento; o desligamento é posto pelo comerciante no painel de carrinho abandonado. Nenhum dos dois é levantado pela requisição.

Receita: Shopify (Flow)

O Shopify Flow faz esse mesmo POST sem código e sem um servidor seu no meio: é o exemplo guiado do contrato acima, com o disparo do abandono já resolvido. Usa Shopify? Siga a receita guiada →

Testar a integração

Não existe um ambiente sandbox. O dashboard sempre cria keys do tipo live e não há um ambiente isolado atrás delas — veja Autenticação. O ambiente de teste desta integração se diferencia pelo telefone de destino, não pela credencial.
  • Fase de teste — envie para telefones da sua própria equipe, em uma janela combinada. A mensagem chega de verdade, com o template real: essa é exatamente a prova.
  • Fase produtiva — mesma key, mesmo contrato; só muda que os telefones são de clientes reais.
A lista de telefones de destino é, portanto, o único controle real entre uma fase e a outra: combine-a por escrito antes do primeiro teste e trate-a como parte da configuração da integração.