Mensagens

Um endpoint só, para os dois casos: texto livre dentro da janela de 24h e template aprovado fora dela. O que decide é o corpo que você manda.

POST/public/v1/messages

Texto livre (janela de 24h) (link para esta seção)

Vale quando o contato escreveu para você nas últimas 24 horas. É a regra da Meta, não nossa, e existe para você não virar canal de spam.

Texto livre não custa nada; template é cobrado. Antes de escolher, pergunte em GET /window/{to} se a janela está aberta. Uma chamada barata que evita pagar template para quem responderia de graça.
curl
curl -X POST https://api.patrociniotech.com/public/v1/messages \
  -H "Authorization: Bearer ptk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "5521999999999",
    "text": "Seu pedido saiu para entrega.",
    "client_ref": "pedido-8421"
  }'

Template aprovado (fora da janela) (link para esta seção)

É o caso de campanha, cobrança e aviso que parte de você. Crie e aprove o template no painel, em Templates, e mande o nome dele aqui. variables preenche {{1}}, {{2}} e assim por diante, na ordem.

curl
curl -X POST https://api.patrociniotech.com/public/v1/messages \
  -H "Authorization: Bearer ptk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "5521999999999",
    "template": "aviso_entrega",
    "language": "pt_BR",
    "variables": ["Maria", "8421"],
    "client_ref": "pedido-8421"
  }'

Template com anexo (bilhete, comprovante, nota) (link para esta seção)

Template cujo cabeçalho é documento, imagem ou vídeo exige o anexo no envio. É o caso de entrega de pedido com o bilhete em PDF. Descubra quais dos seus templates precisam disso em Templates.

curl
curl -X POST https://api.patrociniotech.com/public/v1/messages \
  -H "Authorization: Bearer ptk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "5521999999999",
    "template": "entrega_pedido",
    "language": "pt_BR",
    "header_media": {
      "type": "document",
      "link": "https://sualoja.com.br/bilhetes/8421.pdf",
      "filename": "Bilhete 8421.pdf"
    },
    "button_url_param": "8421",
    "client_ref": "pedido-8421"
  }'
O link precisa ser uma URL pública: quem baixa o arquivo é a Meta, e ela não faz login no seu sistema. Não é upload, o arquivo continua no seu servidor. Sem o anexo, o envio falha com 132012.

O filename vale só para documento, e é o nome que a pessoa vê no WhatsApp. Guardamos ele junto da mensagem, então o atendente que abrir a conversa no painel vê qual bilhete foi enviado, e não só a palavra “template”.

Cobrança PIX (template de detalhes do pedido) (link para esta seção)

O cliente recebe um cartão com os itens e o total, e paga por PIX sem sair do WhatsApp. Só funciona em template criado no formato de cobrança (em Templates, campo Formato) e com pagamentos habilitados na sua conta da Meta.

Todo valor é inteiro, em centavos: R$ 495,75 é 49575. E a conta precisa fechar: a soma dos itens mais a taxa tem que dar o total, senão a chamada é recusada antes de sair daqui. Um valor errado no cartão de cobrança já é uma cobrança errada.
curl
curl -X POST https://api.patrociniotech.com/public/v1/messages \
  -H "Authorization: Bearer ptk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "5521999999999",
    "template": "cobranca_emissao",
    "language": "pt_BR",
    "pix_order": {
      "reference_id": "MP-PED-8421",
      "pix_code": "00020101021226700014br.gov.bcb.pix...",
      "merchant_name": "Sua Empresa LTDA",
      "key": "39580525000189",
      "key_type": "CNPJ",
      "items": [
        { "name": "Emissao de passagem", "amount": 49575, "quantity": 1 }
      ],
      "tax": 0
    },
    "client_ref": "pedido-8421"
  }'

O pix_code é o copia e cola que o seu PSP gerou para esta cobrança, e vai inteiro. O reference_id é o seu número do pedido: é por ele que você concilia o pagamento depois. key_type aceita CNPJ, CPF, EMAIL, PHONE e EVP (chave aleatória).

Campos do corpo (link para esta seção)

CampoObrigatórioO que é
tosimNúmero do destinatário, só dígitos, com país e DDD. Ex.: 5521999999999.
textum dos doisTexto livre. Só funciona dentro da janela de 24h. Até 4096 caracteres.
templateum dos doisNome do template aprovado. Use fora da janela.
languagenãoIdioma do template. Padrão pt_BR.
variablesnãoValores de {{1}} em diante, na ordem. Até 20.
variable_namesnãoNomes, quando o template usa variáveis nomeadas ({{pedido}}) em vez de numeradas. Mesmo tamanho de variables.
header_mediadependeAnexo do cabeçalho: {type, link, filename}. Obrigatório quando o template tem cabeçalho de documento, imagem ou vídeo.
header_textdependeValor da variável de um cabeçalho de TEXTO. Obrigatório quando o template tem uma.
button_url_paramdependeTrecho que a Meta anexa ao fim da URL de um botão dinâmico. Ex.: o id do pedido, ou o token de um link de acesso.
coupon_codedependeCódigo que o cliente copia no botão de cupom (COPY_CODE). Obrigatório quando o template tem esse botão; muda por campanha.
coupon_indexnãoPosição do botão de cupom na lista de botões do template. O padrão é 0.
button_url_indexnãoPosição do botão de link na lista de botões do template, começando em 0. Só precisa mandar quando o template tem mais de um botão e o de link não é o primeiro (ex.: uma resposta rápida antes dele). O padrão é 0.
pix_orderdependeCobrança PIX. Obrigatório quando o template é de cobrança. Valores em centavos, e a soma precisa fechar com o total.
reply_to_message_idnãoResponder citando uma mensagem. Use o message_id que veio em message.received.
phone_number_idnãoNúmero seu que dispara. Sem ele, usa o número padrão da conta.
client_refnãoSeu identificador (id do pedido, do cliente, da linha da campanha). Volta em todo evento de status.

client_ref: o campo mais útil daqui (link para esta seção)

Sem ele, para saber quem recebeu o quê você precisaria guardar uma tabela de-para entre o id do WhatsApp e o seu registro. Com ele, o seu próprio identificador volta em todo evento do webhook, e o de-para deixa de existir.

Numa campanha, mande a linha da campanha em client_ref. Quando o status chegar, você marca aquela linha direto, sem consultar nada.

Resposta (link para esta seção)

200
{
  "success": true,
  "data": {
    "message_id": "wamid.HBgNNTUyMT...",
    "status": "sent",
    "to": "5521999999999",
    "client_ref": "pedido-8421"
  }
}
status: sent quer dizer que a Meta aceitou o envio, não que a pessoa recebeu. Entrega, leitura e falha chegam depois, pelo webhook. Nunca conte entrega pela resposta deste endpoint.

Não enviar duas vezes (link para esta seção)

Mande o header Idempotency-Key com um valor único por mensagem (um uuid serve). Se a mesma chamada chegar de novo, devolvemos a resposta original em vez de enviar outra vez. Vale por pelo menos 24 horas.

header
Idempotency-Key: 6f1c2b7e-1f0a-4c3d-9a2b-5e8d1f0a4c3d

Isso existe por um motivo concreto: a resposta só volta depois que falamos com a Meta. Se a sua conexão cair nesse meio, o seu retry natural faria a pessoa receber duas vezes, e a conversa ser cobrada duas vezes. Com a chave, repetir é inofensivo. A resposta repetida vem com idempotent_replay: true.

A chave é reservada antes de falarmos com a Meta, e não depois. A diferença importa justamente no caso que ela existe para cobrir: a janela em que a sua conexão cai é a da chamada à Meta, então guardar só no fim deixaria o seu retry chegar antes de existir qualquer registro. São três respostas possíveis:

SituaçãoO que você recebeO que fazer
Chave nova200 com a mensagem enviadaSeguir.
Chave já concluída200 com a resposta original e idempotent_replay: trueSeguir: a mensagem saiu uma vez só.
Chave em andamento409 idempotency_in_flightEsperar alguns segundos e repetir COM A MESMA CHAVE. É esta resposta que impede o envio duplo quando duas chamadas correm juntas.
Se o envio falhar (template errado, número inválido, janela fechada), a chave é liberada: você corrige o pedido e pode repetir com a mesma chave. Ela só fica presa quando a mensagem realmente saiu.
Repita em erro de CONEXÃO e em 5xx, sempre reusando a mesma chave. É o único jeito de não perder mensagem quando a rede cai no meio, e com a chave repetir não duplica. Uma reserva que ficou órfã (o processo do nosso lado caiu entre reservar e responder) expira em 5 minutos e a chave volta a valer sozinha.

Validar o número antes (link para esta seção)

POST/public/v1/validate

Confere o formato do número (DDI errado, tamanho errado, digitação) e devolve normalizado, pronto para usar no envio. Útil antes de importar uma lista grande.

curl
curl -X POST https://api.patrociniotech.com/public/v1/validate \
  -H "Authorization: Bearer ptk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"to": "(21) 99999-9999", "country": "BR"}'
Valida o FORMATO, não a existência no WhatsApp: a Cloud API não expõe esse teste. Saber se o número existe de verdade só vem do envio somado ao status que chega no webhook.