+
AB

Por Amy Baker

Publicado em 25 de jul. de 2026

Como enviar uma mensagem de modelo do WhatsApp usando a API

Está criando um sistema de notificações pelo WhatsApp? Este guia aborda as três maneiras de enviar mensagens de modelo pela API da Chakra, os dois padrões diferentes de substituição de variáveis, como lidar com os erros 131049 e 131026, o rastreamento de entrega por webhooks e o envio em massa com limite de taxa.

Se você está criando sistemas de notificação pelo WhatsApp — atualizações de pedidos, lembretes de consultas, OTPs, fluxos para reengajar clientes —, em algum momento precisará enviar uma mensagem de modelo programaticamente. A Chakra oferece três maneiras diferentes de fazer isso, cada uma adequada a um padrão de integração. Este guia aborda as três: a estrutura das solicitações, quando usar cada uma, a substituição de variáveis e como lidar com os dois erros mais comuns em produção — 131049 e 131026.

Este guia parte do princípio de que você já sabe "o que é um modelo do WhatsApp" — tem uma WABA aprovada, modelos aprovados e uma chave de API. Se precisar configurar isso, acesse a seção de chaves de API da Chakra para gerar seu token bearer.

Autenticação: um resumo

Toda solicitação precisa de um token bearer gerado a partir de uma chave de API com o escopo de função/privilégio adequado:

curl --request GET \  --url https://api.chakrahq.com/v1/ext/config \  --header 'Authorization: Bearer <your_access_token>'

Restrinja as chaves ao mínimo de privilégios necessário, especialmente qualquer coisa que envolva código do lado do cliente. Também vale saber desde já: a Chakra impõe um limite de taxa de 200 chamadas de API/minuto — leve isso em conta em qualquer rotina de envio em massa, em vez de descobrir por meio de erros 429 em produção.

As três maneiras de enviar uma mensagem de modelo

MétodoPadrão do endpointIdeal para
Por processo.../process/{procedureShortId}/{primaryKeyOrId}/send-template-messageEnvios vinculados a um registro do CRM — um lead, ticket ou caso já cadastrado na Chakra
Por número de telefone.../phoneNumber/{phoneNumber}/send-template-messageEnvios simples e independentes, quando você já tem o número do destinatário e não precisa de um registro vinculado
Formato da API da Meta.../api/{whatsappApiVersion}/{whatsappPhoneNumberId}/messagesControle total, com o payload exato da Meta Cloud API — útil se você estiver migrando um código de integração existente com a Meta ou precisar de tipos de mensagem que os endpoints simplificados da Chakra não abstraem

As três opções usam a mesma infraestrutura do WhatsApp — a diferença está no formato do payload e no contexto que a Chakra associa ao envio.

Método 1: enviar por processo

Use este método quando a mensagem estiver vinculada a um registro já existente na plataforma da Chakra — um lead, ticket, visita de campo ou qualquer tipo de processo personalizado. É a opção certa quando você quer que a mensagem enviada seja registrada automaticamente no registro, sem precisar associá-los em uma etapa separada.

Endpoint:

POST https://api.chakrahq.com/v1/ext/plugin/whatsapp/{pluginId}/process/{procedureShortId}/{primaryKeyOrId}/send-template-message
  • pluginId — o UUID da sua conexão do WhatsApp, que pode ser copiado na página de detalhes do plugin na tela de configuração
  • procedureShortId — o tipo de registro (lead, ticket, caseou um shortId de processo personalizado)
  • primaryKeyOrId — o ID ou a chave primária do registro específico

Exemplo com curl:

curl --location --request POST \  'https://api.chakrahq.com/v1/ext/plugin/whatsapp/d83e1d23-50b8-4d87-8f92-842a0ac516f6/process/lead/8842/send-template-message' \  --header 'Authorization: Bearer <token>' \  --header 'Content-Type: application/json' \  --data-raw '{    "whatsappPhoneNumberId": "775966265503012",    "templateName": "order_confirmation",    "mapping": [      { "schemaPropertyName": "1", "schemaPropertyValue": "John" },      { "schemaPropertyName": "2", "schemaPropertyValue": "ORD-4521" }    ]  }'

JavaScript (fetch):

async function sendTemplateForLead(leadId, templateName, variables) {  const response = await fetch(    `https://api.chakrahq.com/v1/ext/plugin/whatsapp/${PLUGIN_ID}/process/lead/${leadId}/send-template-message`,    {      method: "POST",      headers: {        "Authorization": `Bearer ${API_TOKEN}`,        "Content-Type": "application/json",      },      body: JSON.stringify({        whatsappPhoneNumberId: PHONE_NUMBER_ID,        templateName,        mapping: variables.map((value, i) => ({          schemaPropertyName: String(i + 1),          schemaPropertyValue: value,        })),      }),    }  );  if (!response.ok) {    const error = await response.json();    throw new Error(`Template send failed: ${JSON.stringify(error)}`);  }  return response.json();}

Método 2: enviar por número de telefone

O payload é funcionalmente idêntico ao do Método 1, mas o envio é feito diretamente para um número de telefone, em vez de um registro de processo vinculado. Use este método quando a associação a um registro não for relevante — por exemplo, em um envio acionado por webhook de um sistema externo (Shopify, WooCommerce ou seu próprio backend) que não precise de um lead correspondente na Chakra.

Endpoint:

POST https://api.chakrahq.com/v1/ext/plugin/whatsapp/{pluginId}/phoneNumber/{phoneNumber}/send-template-message

Exemplo com curl:

curl --location --request POST \  'https://api.chakrahq.com/v1/ext/plugin/whatsapp/d83e1d23-50b8-4d87-8f92-842a0ac516f6/phoneNumber/919901258433/send-template-message' \  --header 'Authorization: Bearer <token>' \  --header 'Content-Type: application/json' \  --data-raw '{    "whatsappPhoneNumberId": "775966265503012",    "templateName": "shipping_update",    "mapping": [      { "schemaPropertyName": "1", "schemaPropertyValue": "Priya" }    ],    "imageUrl": "https://your-cdn.com/shipping-banner.png"  }'

JavaScript (fetch):

async function sendTemplateByPhone(phoneNumber, templateName, mapping, imageUrl) {  const body = { whatsappPhoneNumberId: PHONE_NUMBER_ID, templateName, mapping };  if (imageUrl) body.imageUrl = imageUrl;  const response = await fetch(    `https://api.chakrahq.com/v1/ext/plugin/whatsapp/${PLUGIN_ID}/phoneNumber/${phoneNumber}/send-template-message`,    {      method: "POST",      headers: {        "Authorization": `Bearer ${API_TOKEN}`,        "Content-Type": "application/json",      },      body: JSON.stringify(body),    }  );  return handleWhatsAppResponse(response); // shared error handler, see below}

Observe o campo imageUrl — se o seu modelo tiver um componente de imagem no cabeçalho, informe aqui uma URL acessível publicamente, em vez de fazer upload da mídia separadamente. A exceção é quando você usa o endpoint no formato da Meta, que espera um ID de mídia.

Método 3: formato da API da Meta (repasse direto)

Este endpoint é uma camada fina de integração — a Chakra encaminha seu payload diretamente para a Cloud API da Meta, usando o próprio esquema da Meta. Use este método quando você já tiver uma lógica de payload no formato da Meta (migrada de uma integração anterior diretamente com a Cloud API ou criada com base na documentação da Meta), precisar de tipos de mensagem ou componentes que os endpoints simplificados da Chakra não oferecem ou quiser paridade exata, byte a byte, com os contratos de solicitação/resposta da Meta para seus próprios registros.

Endpoint:

POST https://api.chakrahq.com/v1/ext/plugin/whatsapp/{pluginId}/api/{whatsappApiVersion}/{whatsappPhoneNumberId}/messages

Exemplo com curl:

curl --location --request POST \  'https://api.chakrahq.com/v1/ext/plugin/whatsapp/d83e1d23-50b8-4d87-8f92-842a0ac516f6/api/v19.0/775966265503012/messages' \  --header 'Authorization: Bearer <token>' \  --header 'Content-Type: application/json' \  --data-raw '{    "messaging_product": "whatsapp",    "recipient_type": "individual",    "to": "919901258433",    "type": "template",    "template": {      "name": "order_confirmation",      "language": { "policy": "deterministic", "code": "en_US" },      "components": [        { "type": "body", "parameters": ["John", "ORD-4521"] }      ]    }  }'

JavaScript (fetch):

async function sendTemplateMetaFormat(to, templateName, langCode, bodyParams) {  const response = await fetch(    `https://api.chakrahq.com/v1/ext/plugin/whatsapp/${PLUGIN_ID}/api/v19.0/${PHONE_NUMBER_ID}/messages`,    {      method: "POST",      headers: {        "Authorization": `Bearer ${API_TOKEN}`,        "Content-Type": "application/json",      },      body: JSON.stringify({        messaging_product: "whatsapp",        recipient_type: "individual",        to,        type: "template",        template: {          name: templateName,          language: { policy: "deterministic", code: langCode },          components: [{ type: "body", parameters: bodyParams }],        },      }),    }  );  return handleWhatsAppResponse(response);}

Esse mesmo endpoint também aceita os payloads nativos da Meta para mensagens de sessão (texto livre) — texto, imagem e listas interativas —, pois repassa as solicitações diretamente para a API /messages subjacente, não sendo um endpoint exclusivo para modelos.

Substituição de variáveis: dois padrões diferentes

Isso costuma confundir quem alterna entre os métodos, então vale explicar claramente:

Os Métodos 1 & 2 (abstração da Chakra) usam um array de mapping com pares { schemaPropertyName, schemaPropertyValue } . schemaPropertyName é a posição da variável no modelo, em formato de string ("1", "2"etc.), e schemaPropertyValue é o valor usado na substituição. Esse é o esquema simplificado da própria Chakra, independente da estrutura bruta de componentes da Meta.

O Método 3 (formato da Meta) usa o array nativo de components da Meta, em que cada componente (header, body, button) tem seu próprio array de parameters , e as variáveis são preenchidas sequencialmente. Se o corpo do seu modelo tiver várias variáveis, parameters: ["John", "ORD-4521"] preenche {{1}} e {{2}} respectivamente, na ordem do array — não há um campo de índice explícito, então é importante acertar a ordem.

Se você estiver criando um sistema que possa alternar entre endpoints no futuro (por exemplo, começando com uma solução simples e depois precisando dos recursos brutos da Meta), vale criar uma função adaptadora interna que converta sua própria lista de variáveis para o formato esperado pelo endpoint usado, em vez de duplicar a lógica de mapeamento de variáveis em toda a base de código.

Como lidar com os erros que você realmente vai encontrar

Erro 131049 — engajamento no ecossistema / mensagem não entregue. Não se trata de um erro de solicitação malformada; o sistema do WhatsApp está recusando a entrega porque provavelmente o destinatário não quer receber a mensagem (baixo engajamento recente, muitos bloqueios ou limite de marketing por usuário atingido entre empresas, em geral). Não tente enviar a mesma mensagem novamente logo em seguida — uma nova tentativa imediata só repete a falha e pode piorar a situação para esse contato. Registre o erro, exclua o contato do próximo ciclo de envio e tente novamente após um intervalo (24 horas é um mínimo razoável), usando um modelo de Utility se o contexto permitir.

Erro 131026 — destinatário não está no WhatsApp / não pode ser alcançado. O número não é válido no WhatsApp, não aceitou os termos atuais ou não pode receber esse tipo de mensagem. Trata-se de um problema de qualidade dos dados, não de algo temporário — tentar novamente não vai resolver. Sinalize o contato para limpeza da lista, em vez de colocá-lo de novo na fila.

Um manipulador de erros compartilhado pelos três métodos poderia ser assim:

async function handleWhatsAppResponse(response) {  if (response.ok) return response.json();  const errorBody = await response.json();  const errorCode = errorBody?.error?.code || errorBody?._data?.error?.code;  switch (errorCode) {    case 131049:      // Don't retry now. Queue for a delayed retry, exclude from current campaign.      await flagForDelayedRetry(errorBody, { minDelayHours: 24 });      break;    case 131026:      // Bad number — don't retry, flag for cleanup.      await flagInvalidNumber(errorBody);      break;    default:      // Log and surface unexpected errors for manual review.      console.error("Unhandled WhatsApp send error:", errorBody);  }  throw new Error(`WhatsApp send failed (${errorCode}): ${JSON.stringify(errorBody)}`);}

Implementar esse tipo de classificação no seu fluxo de envio desde o início evita que uma rotina de novas tentativas continue bombardeando silenciosamente os mesmos números que falham — uma das maneiras mais rápidas de prejudicar sua Classificação de Qualidade.

Como acompanhar o status de entrega após o envio

Uma resposta 200 de qualquer um dos três endpoints apenas confirma que a mensagem foi aceita para envio — não confirma a entrega nem o status de leitura. Para isso, você precisa consumir o webhook de eventos recebidosdo Chakra, que envia atualizações de status (sent, delivered, read, failed) assim que acontecem, associadas ao whatsappMessageId ou ao externalId retornado na resposta original de envio.

Um handler de webhook básico:

app.post("/webhooks/whatsapp", (req, res) => {  const event = req.body;  if (event.type === "message_status") {    const { messageId, status, errorCode } = event.data;    // status: sent | delivered | read | failed    updateMessageRecord(messageId, { status, errorCode });    if (status === "failed" && errorCode) {      handleWhatsAppResponse({ ok: false, json: async () => ({ error: { code: errorCode } }) });    }  }  res.sendStatus(200); // acknowledge quickly; don't block on downstream processing});

Armazenar o whatsappMessageId junto com seu próprio registro (ID do pedido, ID do lead ou o que tiver acionado o envio) no momento do envio é o que torna esse webhook realmente útil — caso contrário, você terá que associar os eventos de status ao contexto depois que eles acontecerem.

Envio em grande volume: respeitando o limite de taxa

Com 200 chamadas por minuto, um loop ingênuo para alguns milhares de destinatários vai atingir erros 429 rapidamente. Um padrão simples de fila e controle de taxa resolve isso sem complicar demais:

async function sendBatch(recipients, templateName, buildMapping) {  const RATE_LIMIT = 200; // calls per minute  const DELAY_MS = Math.ceil(60000 / RATE_LIMIT);  for (const recipient of recipients) {    try {      await sendTemplateByPhone(recipient.phone, templateName, buildMapping(recipient));    } catch (err) {      console.error(`Send failed for ${recipient.phone}:`, err.message);      // classification already handled inside handleWhatsAppResponse    }    await new Promise((resolve) => setTimeout(resolve, DELAY_MS));  }}

Para qualquer volume acima de alguns milhares de envios, vale a pena configurar uma fila de tarefas adequada (BullMQ, SQS ou similar), com backoff para novas tentativas, em vez de usar um loop em processo — ela sobrevive à reinicialização do processo no meio do lote, ao contrário de um simples loop for .

Por que desenvolver isso no Chakra

Veja alguns pontos importantes se você está avaliando onde desenvolver essa integração:

  • Documentação completa — trechos de código, exemplos de solicitações e respostas e tutoriais de webhook para todos os endpoints, para você não precisar descobrir como tudo funciona na tentativa e erro
  • Infraestrutura empresarial — disponibilidade de 99.9%, com lógica integrada de novas tentativas e tratamento de limites de taxa do lado do Chakra, algo importante quando você envia em grande volume e não pode correr o risco de perder mensagens sem perceber
  • Uma camada fina e transparente — o endpoint no formato Meta é realmente apenas uma passagem direta, então tudo o que está na documentação da própria Meta continua valendo; você não precisa aprender uma camada de abstração proprietária para cada caso de uso
  • Preços transparentes, sem margem adicional — você paga as tarifas padrão da Meta pelas mensagens de modelo, sem nenhuma margem oculta adicionada
  • Webhooks em tempo real — eventos recebidos e mudanças no status de entrega ficam disponíveis assim que acontecem, para que seu sistema possa reagir a falhas (como os casos 131049/131026 acima) sem precisar consultar repetidamente a API

Se você está desenvolvendo um sistema de notificações que precisa ir além de alguns envios de teste, vale a pena usar diretamente a API do Chakra em vez de conectar por conta própria chamadas de nível mais baixo da Meta Cloud API — a lógica de novas tentativas, o tratamento de limites de taxa e o envio vinculado ao CRM já estão disponíveis.

Consiga sua chave de API e comece a enviar — cadastre-se, conecte sua WABA e você poderá enviar sua primeira mensagem de modelo em poucos minutos.