Sign inGet Started

Enviando mensagens WhatsApp

Este guia cobre o endpoint de envio, POST /v1/whatsapp/messages. Você monta um payload JSON com um destinatário e exatamente um tipo de conteúdo: um template pré-aprovado ou uma mensagem de serviço com texto, imagem, vídeo, áudio, sticker, documento, localização, cartões de contato ou algo para tocar. Bird retorna 202 Accepted com um ID de mensagem e entrega de forma assíncrona. Qual dos dois você pode enviar depende da janela de atendimento ao cliente. Cada requisição envia uma mensagem para um destinatário, e não existe endpoint de envio em lote.

Um envio mínimo

O menor payload válido é um destinatário to e um template com seu slug. Adicione language se quiser um idioma específico; omitir envia o idioma padrão do template, e preencha as variáveis que o template declara por meio de components.
A chamada curl indica o host dos EUA; se sua chave começa com bk_eu1_, use https://eu1.platform.bird.com. Os SDKs leem a região a partir da sua chave, então não definem host.
const msg = await bird.whatsapp.send({
  to: "+15551234567",
  template: {
    slug: "bird_otp",
    components: [{ type: "body", parameters: [{ type: "text", text: "123456" }] }],
  },
});
console.log(msg.id, msg.status);

A janela de atendimento ao cliente

Qual dos dois você pode enviar depende de um único estado: se a janela de atendimento ao cliente está aberta.
O contato abre a janela ao enviar uma mensagem ou ligar para o seu número comercial, e ela permanece aberta por 24 horas, reiniciando cada vez que ele envia uma nova mensagem. Enquanto está aberta, você pode enviar uma mensagem de serviço, ou seja, qualquer conteúdo livre: texto, imagem, vídeo, áudio, sticker, documento, localização ou interativo. Quando ela expira, apenas um template pré-aprovado chega ao contato, e a resposta dele ao template reabre a janela.
Bird rastreia a janela para você, então uma mensagem de serviço enviada para uma janela fechada é recusada antes de qualquer coisa ser criada ou cobrada: a requisição retorna um 422 E15044 WhatsAppServiceWindowClosed. A verificação é best effort e falha aberta, então um 202 não é prova de que a janela estava realmente aberta no momento do despacho; uma janela que expira entre o aceite e o despacho falha de forma assíncrona, com service_window_expired no last_error da mensagem.
Consulte a janela de atendimento ao cliente para o ciclo completo: o que a abre, o que a reinicia e como ela interage com a cobrança.

Montando o payload

Destinatário

to é um único destinatário, informado como número de telefone ou como ID de usuário com escopo de negócio. Um número de telefone está no formato E.164: um + inicial, código do país e número do assinante, como +14155550100. Nós validamos o número, então um valor que não pode ser um número real e discável (comprimento incorreto, prefixo não atribuído) é rejeitado com um 422 WhatsAppInvalidRecipient antes de qualquer cobrança. Uma mensagem vai para um destinatário; não há array de destinatários nem envio em lote, então alcance várias pessoas com uma chamada por destinatário.
Um ID de usuário com escopo de negócio como US.13491208655302741918 endereça um contato cujo número de telefone você não tem, que é como você responde a um contato que entrou em contato sem número. Duas coisas mudam: o número de envio precisa pertencer ao mesmo portfólio de negócios ao qual o ID está vinculado, e um template de código de verificação de uso único precisa de um número de telefone. Um template gerenciado por Bird é recusado no aceite com um 422 WhatsAppRecipientNotSupportedForTemplate; um template de autenticação que seu espaço de trabalho criou é aceito e depois falha, pois a Meta exige um número de telefone para ele.

Template

template indica o template pré-aprovado a enviar:
  • slug (obrigatório): o slug do template, como bird_order_confirmation. Deve corresponder a um template do seu catálogo (letras minúsculas, dígitos e underscores).
  • language: a tag de idioma do template, como en ou pt-BR. Omita para enviar no idioma padrão do template; informar um idioma que o template não possui retorna um 422 que lista os disponíveis. A mensagem aceita ecoa o idioma resolvido.
  • components: os valores que preenchem as variáveis do template (consulte Componentes e parâmetros). Omita para um template sem variáveis.
Navegue pelos seus templates, seus idiomas e uma pré-visualização renderizada de cada um na página Templates.

Componentes e parâmetros

Templates carregam variáveis, nomeadas ({{ref}}, {{amount}}) ou numeradas ({{1}}, {{2}}). Você fornece os valores por meio de components. Cada componente indica um type (body ou button) e um array parameters. Cada parâmetro indica seu próprio type (text, image, video, gif, document ou location) e carrega o campo correspondente: text uma string simples, image/video/gif/document uma https url pública, e location um ponto no mapa. Um template com parâmetros nomeados exige um name em cada parâmetro, correspondendo exatamente aos nomes que o template declara (consulte Referência de campos). Um template posicional omite name e recebe os valores na ordem de {{n}}, então o primeiro parâmetro preenche {{1}}. De qualquer forma, parâmetros que não correspondem ao que o template declara retornam um 422 WhatsAppTemplateParameterMismatch. Um tipo de componente header também existe no protocolo: em um template gerenciado por Bird ele é descartado, pois nenhum template gerenciado por Bird declara uma variável de cabeçalho, mas em um template que seu espaço de trabalho criou ele é encaminhado, que é como um template utilitário ou de marketing com cabeçalho de mídia recebe sua imagem.
Por exemplo, um template de código de verificação de uso único cujo corpo diz {{1}} is your verification code e cujo botão copia o código recebe o código como parâmetro de corpo e como parâmetro de botão, posicionalmente (sem name):
Exemplo de código
{
  "components": [
    { "type": "body", "parameters": [{ "type": "text", "text": "481920" }] },
    { "type": "button", "parameters": [{ "type": "text", "text": "481920" }] }
  ]
}

Categoria e remetente

A categoria de um template (authentication, utility ou marketing) determina como WhatsApp trata a mensagem e, junto com o país de destino, quanto ela custa.
Quem é o dono do remetente determina se você o informa:
  • Um template gerenciado por Bird (cujo slug começa com bird_) envia a partir do número que Bird mantém para aquela categoria, então omita from. Defini-lo retorna um 422 WhatsAppSenderNotAllowed.
  • Qualquer outro caso informa seu próprio remetente em from: uma mensagem de serviço de qualquer tipo e qualquer template que seu espaço de trabalho criou. O número deve pertencer ao seu espaço de trabalho. Omiti-lo retorna um 422 WhatsAppSenderRequired, e um número do qual o espaço de trabalho não pode enviar retorna um 422 WhatsAppSenderNotFound. Um template criado também precisa estar na mesma WhatsApp Business Account que o número, ou o envio retorna um 422 WhatsAppSenderWABAMismatch.
Configuração de número de telefone cobre ambos os tipos de número e como um número próprio é conectado.

Mensagens de serviço

Em vez de template, carregue exatamente um entre text, image, video, audio, sticker, document, location, contact_cards ou interactive. Todos os nove são mensagens de serviço, então precisam de uma janela de atendimento ao cliente aberta. Todos também exigem from, um número que seu espaço de trabalho possui; os números gerenciados de Bird não podem transportá-lo.
  • text: { "body": "..." }, até 4.096 caracteres. Adicione "preview_url": true para renderizar uma pré-visualização de link para a primeira URL em body.
  • image, video, audio, sticker, document: cada um recebe uma URL https pública que WhatsApp busca no momento do envio (url), então uma URL assinada precisa sobreviver ao envio. Uma URL http é rejeitada diretamente. WhatsApp busca o arquivo em si, então uma URL que ele não consegue acessar, que serve um tipo não suportado ou um arquivo acima do limite de tamanho para seu tipo é aceita e depois falha, com media_rejected no last_error da mensagem e a razão do próprio WhatsApp em description. image, video e document também aceitam um caption opcional; document também aceita um filename opcional; audio aceita um flag voice opcional para renderização como nota de voz.
  • location: { "latitude": ..., "longitude": ... } (ambos obrigatórios, graus decimais) mais name e address opcionais.
  • contact_cards: um array de até cinco contatos compartilhados em uma mensagem. O name de cada cartão precisa de formatted_name mais pelo menos uma outra parte (first_name, last_name, middle_name, prefix ou suffix); phone_numbers, emails, urls e addresses aceitam até dez entradas cada, e org e birthday (como YYYY-MM-DD) são opcionais. Um phone_number em E.164 ganha naquele cartão um botão que abre um chat com ele.
  • interactive: texto do corpo mais algo para tocar, em um de seis tipos: botões de resposta, um menu de lista, um botão de link, um carrossel de mídia ou um botão único pedindo ao destinatário sua localização ou seu número de telefone. Mensagens interativas cobre a estrutura de cada tipo, as respostas que um toque produz e os limites.
Exemplo de código
{
  "to": "+16505551234",
  "from": "+13124495648",
  "text": { "body": "Your order shipped: https://example.com/track/A1B2C3", "preview_url": true }
}
Uma requisição sem conteúdo, ou com mais de um tipo, é rejeitada com um 422.

Citando uma mensagem

Defina in_reply_to_message_id como um ID de mensagem WhatsApp para enviar sua mensagem como resposta a ela, da mesma forma que tocar em responder no app WhatsApp cita uma mensagem. O destinatário vê sua mensagem com a citada acima, e o campo retorna em toda leitura da mensagem.
Funciona no sentido inverso também: uma mensagem de entrada que WhatsApp marca como resposta carrega o ID da mensagem citada no mesmo campo, que é como você identifica a qual das suas mensagens uma resposta se refere. Uma mensagem de entrada que WhatsApp não marca não carrega ID, e a resolução também pode falhar. Para correlação confiável, use identificadores explícitos de resposta interativa com o estado de conversa ou tarefa armazenado pela sua aplicação. O metadata de saída permanece no registro de saída e não é copiado automaticamente para a resposta.
A citação é resolvida antes de o envio ser aceito, então uma citação que não pode ser renderizada falha a própria requisição e nada é criado ou cobrado. Um id que não corresponde a nenhuma mensagem que este espaço de trabalho possui, ou que é mais antigo que os 15 dias em que uma mensagem permanece citável, retorna um 404 E15071 WhatsAppReferencedMessageNotFound. Um que corresponde a uma mensagem que nunca chegou a WhatsApp, ou a uma mensagem de uma conversa diferente da do to e from deste envio, retorna um 422 E15072 WhatsAppMessageNotQuotable. Se Bird não consegue acessar o armazenamento que responde à consulta, o envio retorna um 503 E15073 WhatsAppMessageLookupUnavailable, que vale a pena tentar novamente. A citação funciona tanto em um envio de template quanto em um envio de conteúdo livre.
Exemplo de código
{
  "to": "+16505551234",
  "from": "+13124495648",
  "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
  "text": { "body": "Yes, that slot is still free." }
}

Tags e metadados

Dois campos opcionais anexam seu próprio contexto a uma mensagem; ambos retornam nas leituras de API e acompanham cada evento de webhook da mensagem:
  • tags: até 20 rótulos { "name": ..., "value": ... } estruturados para dimensões de baixa cardinalidade pelas quais você filtra e gera relatórios (uma campanha, uma variante de experimento). Nomes e valores aceitam letras ASCII, dígitos, underscore e hífen; nomes têm limite de 32 caracteres e são únicos dentro de um envio, valores de 64. Filtre a lista de mensagens por tag (?tag=campaign ou ?tag=campaign:launch-week), e a página Métricas detalha a entrega por tag.
  • metadata: um objeto JSON arbitrário, até 2 KB serializado, para contexto por envio que você não precisa como dimensão de filtro (um ID de pedido interno, uma referência de sessão).
Exemplo de código
{
  "tags": [{ "name": "campaign", "value": "order-confirmations" }],
  "metadata": { "order_id": "ord_8271" }
}

Referência de campos

CampoTipoObrigatórioLimites / notas
tostringsimUm destinatário por mensagem: um número de telefone E.164 ou um ID de usuário com escopo de negócio, que nenhum template de código de verificação de uso único aceita
fromstring (E.164)não**Omita para um template gerenciado por Bird, que escolhe seu próprio remetente; obrigatório para uma mensagem de serviço e para um template que seu espaço de trabalho criou, e deve ser um número que seu espaço de trabalho possui
template.slugstringnão**Um slug de template que seu espaço de trabalho pode enviar; slugs gerenciados por Bird começam com bird_
template.languagestringnão*Tag de idioma do template (en, pt-BR); omita para enviar no idioma padrão do template
template.componentsarraynãoPreenche as variáveis do template; o type do componente é body ou button
template.components[].parameters[].namestringnão†O placeholder que este valor preenche, como ref; obrigatório e deve corresponder aos nomes declarados do template para um template com parâmetros nomeados, omitido para um posicional
interactiveobjectnão**Texto do corpo mais um tipo de conteúdo tocável; uma mensagem de serviço, então precisa de uma janela de serviço aberta. Consulte Mensagens interativas
in_reply_to_message_idstringnãoUm ID de mensagem WhatsApp que este espaço de trabalho possui, citado na mensagem que você envia; ecoado nas leituras. Consulte Citando uma mensagem
tagsarraynãoAté 20 rótulos {name, value}; nome ≤ 32 chars, valor ≤ 64, nomes únicos
metadataobjectnãoJSON arbitrário, até 2 KB serializado
* language é opcional; omiti-lo envia o idioma padrão do template. † name é obrigatório em cada parâmetro para um template com parâmetros nomeados. Omita para um posicional. Consulte Componentes e parâmetros. ** Carregue exatamente um entre template ou um campo de conteúdo de mensagem de serviço (text, image, video, audio, sticker, document, location, interactive); consulte Mensagens de serviço.

O modelo assíncrono: o que 202 significa

Um envio bem-sucedido retorna 202 Accepted com um ID de mensagem e status: accepted. O 202 é retornado somente após o envio ser aceito de forma durável; ele nunca é aceito e depois descartado silenciosamente. Falhas definitivas que você pode corrigir falham imediatamente com um 422: um destinatário inválido, um slug ou idioma de template desconhecido, uma incompatibilidade de parâmetros ou uma mensagem de serviço enviada para uma janela de atendimento ao cliente fechada (WhatsAppServiceWindowClosed). Uma carteira sem saldo não é uma delas: o envio é aceito, e a mensagem termina rejected com insufficient_balance quando Bird tenta cobrá-la. A entrega real acontece de forma assíncrona: a mensagem passa para sent quando a entregamos a WhatsApp, depois para um status terminal (delivered ou failed) quando o recibo chega, reportado por meio de eventos, webhooks e os endpoints de leitura. Um recibo de leitura é exposto separadamente como um timestamp read_at e um evento whatsapp.read, e não como um status.
Uma nota de privacidade: para templates de categoria authentication, o API nunca retorna os valores preenchidos. O eco do 202 e toda leitura posterior carregam um array components vazio para essas mensagens, então um código de verificação nunca reaparece.

Tentando novamente com segurança

Envie o header Idempotency-Key com um valor único por envio lógico, e as tentativas se tornam seguras. Se sua primeira requisição foi bem-sucedida mas você nunca viu a resposta (timeout, conexão interrompida), reenviá-la com a mesma chave retorna o resultado original em vez de enviar, e cobrar, uma mensagem duplicada. A resposta reenviada carrega um header Idempotency-Replay. Consulte idempotência para formato da chave e retenção.

Recebendo a resposta

Mensagens de entrada chegam no mesmo recurso que as de saída, e cada uma delas reinicia a janela de serviço. Recebendo mensagens WhatsApp cobre a leitura delas pela API, a busca da mídia que um contato enviou e o webhook whatsapp.received.

Custo e cobrança

WhatsApp é cobrado por mensagem, com base na categoria do template e no país do destinatário; consulte preços WhatsApp. Uma mensagem é cobrada em duas etapas, em dois momentos diferentes, e o objeto cost na mensagem reporta ambos:
CampoO que éQuando aparece
transaction_amountTaxa de Bird pelo processamento do envioQuando Bird processa o envio aceito, antes do despacho
passthrough_amountParte da Meta no preço da mensagem, que Bird repassaQuando um recibo delivered ou read aplicável chega
amountA soma dos componentes precificados até o momentoCresce à medida que cada componente chega
currency_codeA moeda da carteira da sua organização, compartilhada por ambos os componentesCom o primeiro componente
Ambos os valores são strings decimais, líquidos de impostos.
Os dois componentes são precificados com base em entradas diferentes. A taxa de Bird usa a categoria do template que você enviou e o país do destinatário, que vem do código de país do número de telefone ou, em um envio endereçado a um ID de usuário com escopo de negócio, do prefixo de duas letras desse ID. A parte da Meta usa a categoria que a própria Meta reporta no recibo aplicável, que pode diferir da do template: a Meta pode reportar authentication-international quando suas regras de destino, localização do negócio e elegibilidade se aplicam. Consulte taxas authentication-international WhatsApp.
O que cost mostra depende de quão longe a mensagem avançou:
  • No 202, cost é null. Nada foi precificado.
  • Após o processamento, transaction_amount está definido e amount é igual a ele. passthrough_amount permanece null.
  • Após um recibo delivered ou read aplicável, uma cobrança da Meta registrada com sucesso preenche passthrough_amount, e amount reflete os componentes registrados.
Um componente null significa que nenhum valor está registrado naquela projeção; não é prova de que a mensagem foi gratuita. Um componente explicitamente precificado em zero mostra "0.00000".
As duas cobranças também falham de formas diferentes. A taxa de Bird falha de forma fechada: quando não consegue ser processada após o 202 porque a carteira não cobre o envio ou a rota não tem preço configurado, a mensagem termina rejected com o código de erro insufficient_balance ou price_not_found, e nada é cobrado. Uma mensagem rejected nunca chegou a WhatsApp, que é o que a separa de failed. A parte da Meta falha de forma aberta: se a carteira não tem saldo suficiente ou a tarifa está ausente quando o recibo chega, a cobrança é ignorada sem reverter o estado observado da mensagem. Sua entrega nunca é retida pela segunda cobrança.
Uma mensagem cobrada por Bird retém essa cobrança de saída se a entrega falhar posteriormente. A taxa da Meta é processada a partir de um callback delivered ou read aplicável quando a Meta reporta preço regular com categoria e destino resolvíveis. Ambos os caminhos de callback usam a mesma identidade de taxa e dependem da deduplicação do serviço de cobrança. Reconcilie recibos repetidos contra registros de cobrança em vez de tratar a projeção da mensagem como um recibo de débito permanente. Preços de serviço ou de entrada gratuita podem zerar o componente da Meta; um componente não resolvido não é evidência de que a mensagem foi gratuita.
Use o livro-razão de cobrança para reconciliação financeira. Os campos cost da mensagem são projeções das cobranças e podem atrasar ou permanecer incompletos. Consulte métricas WhatsApp para a distinção entre observações de mensagem e registros de cobrança.
Eventos WhatsApp não carregam custo. Para ler qualquer componente, leia a mensagem de volta com GET /v1/whatsapp/messages/{id}.

Próximos passos