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);msg = client.whatsapp.send(
to="+31612345678",
template="bird_otp",
language="en",
components=[{"type": "body", "parameters": [{"type": "text", "text": "123456"}]}],
)
print(msg.id, msg.status)code := "123456"
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+15551234567",
Template: "bird_otp",
Language: "en",
Components: []bird.WhatsAppMessageTemplateComponent{{
Type: "body",
Parameters: &[]bird.WhatsAppMessageTemplateComponentParameter{{Type: "text", Text: &code}},
}},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)$message = $bird->whatsapp->send(
to: '+15551234567',
template: 'bird_otp',
language: 'en',
components: [
(new WhatsAppMessageTemplateComponent())
->setType('body')
->setParameters([
(new WhatsAppMessageTemplateComponentParameter())->setType('text')->setText('123456'),
]),
],
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--components '[{"parameters":[{"text":"1234","type":"text"}],"type":"body"},{"parameters":[{"text":"1234","type":"text"}],"type":"button"}]' \
--language en \
--template bird_otp \
--to +31612345678curl -X POST "https://{region}.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+31612345678",
"template": {
"slug": "bird_otp",
"language": "en",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "1234"
}
]
},
{
"type": "button",
"parameters": [
{
"type": "text",
"text": "1234"
}
]
}
]
}
}'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 identifica um destinatário, informado como número de telefone, ID de usuário com escopo de negócio ou ID de grupo. O número de telefone segue o formato E.164: um + inicial, código de país e número do assinante, como +14155550100. O número é validado, então um valor que não pode ser um número real e discável (comprimento incorreto, prefixo não atribuído) é recusado com um 422 WhatsAppInvalidRecipient antes de qualquer cobrança. Não existe array de destinatários nem envio em lote, então alcançar várias pessoas que não estão em um mesmo grupo exige 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.
to aceita mais uma forma: um ID de grupo WhatsApp como wag_01krdgeqcxet5s7t44vh8rt9mg, que envia para todos os participantes daquele grupo. Um envio para grupo omite from e reporta a entrega no nível do grupo em vez de contra um único destinatário, então Enviando para um grupo WhatsApp cobre isso em uma página própria.
Template
template identifica o template pré-aprovado a ser enviado:
- 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 o idioma padrão do template; informar um idioma que o template não possui retorna um 422 listando os disponíveis. A mensagem aceita reflete o idioma resolvido.
- components: os valores que preenchem as variáveis do template (veja 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 seus valores por meio de components. Cada componente informa um type (body ou button) e um array parameters. Cada parâmetro informa seu próprio type (text, image, video, gif, document ou location) e traz 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 (veja Referência de campos). Um template posicional omite name e recebe seus valores na ordem {{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 na transmissão: em um template gerenciado por Bird ele é descartado, já que nenhum template gerenciado por Bird declara uma variável de cabeçalho, mas em um template criado pelo seu espaço de trabalho 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 único cujo corpo diz {{1}} is your verification code e cujo botão copia o código recebe o código tanto como parâmetro de corpo quanto 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 é dono do remetente define se você precisa informá-lo:
- Um template gerenciado por Bird (seu 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 criado pelo seu espaço de trabalho. O número deve pertencer ao seu espaço de trabalho. Omiti-lo retorna um 422 WhatsAppSenderRequired, e um número a partir 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, caso contrário 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, inclua 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 eles também exigem from, um número que o seu espaço de trabalho possui; os números gerenciados de Bird não podem carregá-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 durar mais que o envio. Uma URL http é recusada imediatamente. WhatsApp busca o arquivo em si, então uma URL que não consegue alcançar, uma 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 própria de 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 confere àquele 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 o formato 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 solicitação sem conteúdo, ou com mais de um tipo, é recusada com um 422.
Citando uma mensagem
Defina in_reply_to_message_id com 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 recebida que WhatsApp marca como resposta traz o ID da mensagem citada no mesmo campo, que é como você identifica a qual das suas mensagens a resposta se refere. Uma mensagem recebida que WhatsApp não marca não traz 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 na 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 faz a própria solicitação falhar 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 do to e from deste envio, retorna um 422 E15072 WhatsAppMessageNotQuotable. Se Bird não conseguir alcançar 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 formato 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 labels { "name": ..., "value": ... } estruturados para dimensões de baixa cardinalidade por onde 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 são limitados a 32 caracteres e únicos dentro de um envio, valores a 64. Filtre a lista de mensagens por tag (?tag=campaign ou ?tag=campaign:launch-week), e a página Métricas discrimina 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
| Campo | Tipo | Obrigatório | Limites / observações |
|---|---|---|---|
| to | string | sim | Um destinatário por mensagem: um número de telefone E.164, um ID de usuário com escopo de negócio, que nenhum template de código de verificação único aceita, ou um ID de grupo WhatsApp (wag_…), que envia para todos os participantes daquele grupo |
| from | string (E.164) | não** | Omita para um template gerenciado por Bird, que escolhe seu próprio remetente, e para um envio de grupo, que usa o número do próprio grupo; obrigatório para uma mensagem de serviço e para um template criado pelo seu espaço de trabalho, e deve ser um número que o seu espaço de trabalho possui |
| template.slug | string | não** | Um slug de template que o seu espaço de trabalho pode enviar; slugs gerenciados por Bird começam com bird_ |
| template.language | string | não* | Tag de idioma do template (en, pt-BR); omita para enviar o idioma padrão do template |
| template.components | array | não | Preenche as variáveis do template; type do componente é body ou button |
| template.components[].parameters[].name | string | nã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 |
| interactive | object | nã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. Veja Mensagens interativas |
| in_reply_to_message_id | string | não | Um ID de mensagem WhatsApp que este espaço de trabalho possui, citado na mensagem que você envia; retornado nas leituras. Veja Citando uma mensagem |
| tags | array | não | Até 20 labels {name, value}; nome ≤ 32 chars, valor ≤ 64, nomes únicos |
| metadata | object | não | JSON 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. Veja Componentes e parâmetros.
** Inclua exatamente um entre template ou um campo de conteúdo de mensagem de serviço (text, image, video, audio, sticker, document, location, interactive); veja 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 só é retornado depois que o envio é aceito de forma durável; ele nunca é aceito e depois descartado silenciosamente. Falhas graves 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 como rejected com insufficient_balance quando Bird tenta cobrar. A entrega real acontece de forma assíncrona: a mensagem passa para sent quando a entregamos ao WhatsApp, e 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 é exibido separadamente como um timestamp read_at e um evento whatsapp.read, não como um status.
Uma observação de privacidade: para templates da categoria authentication, o API nunca retorna os valores preenchidos. O eco do 202 e toda leitura posterior trazem um array components vazio para essas mensagens, de modo que 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 novas tentativas se tornam seguras. Se sua primeira solicitação foi bem-sucedida mas você nunca viu a resposta (timeout, conexão perdida), reenviá-la com a mesma chave retorna o resultado original em vez de enviar, e cobrar, uma mensagem duplicada. A resposta reenviada traz um header Idempotency-Replay. Veja idempotência para formato e retenção da chave.
Recebendo a resposta
Mensagens recebidas chegam no mesmo recurso que as enviadas, e cada uma delas reinicia a janela de serviço. Recebendo mensagens WhatsApp cobre como lê-las pela API, como buscar a mídia que um contato enviou e o webhook whatsapp.received.
Custo e cobrança
WhatsApp é precificado por mensagem, com base na categoria do template e no país do destinatário; veja preços WhatsApp. Uma mensagem é cobrada em duas etapas, em dois momentos diferentes, e o objeto cost na mensagem reporta ambas:
| Campo | O que é | Quando aparece |
|---|---|---|
| transaction_amount | Taxa de Bird pelo processamento do envio | Quando Bird processa o envio aceito, antes do despacho |
| passthrough_amount | Parte da Meta no preço da mensagem, que Bird repassa | Quando um recibo delivered ou read aplicável chega |
| amount | A soma dos componentes precificados até o momento | Cresce à medida que cada componente chega |
| currency_code | A moeda da carteira da sua organização, compartilhada por ambos os componentes | Com o primeiro componente |
Ambos os valores são strings decimais, líquidos de impostos.
Note: a service message carries no Meta share until September 30th 2026, and neither does a utility template delivered inside an open customer service window. From October 1st 2026 Meta charges for both, except inside a 72-hour free entry point window, and with 1,000 free service messages per business phone number per month: October 2026 pricing changes.
Os dois componentes são precificados com base em entradas diferentes. A taxa de Bird usa a categoria do template enviado 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 daquele 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. Veja taxas authentication-international WhatsApp.
O que cost retorna depende de quão longe a mensagem chegou:
- No 202, cost é null. Nada foi precificado.
- Após o processamento, transaction_amount é 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 foi registrado naquela projeção; não é prova de que a mensagem foi gratuita. Um componente explicitamente precificado em zero retorna "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 como 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 está insuficiente ou a taxa 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 depois. A taxa da Meta é processada a partir de um callback delivered ou read aplicável quando a Meta reporta precificação regular com uma 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 reenviados contra registros de cobrança em vez de tratar a projeção da mensagem como um recibo de débito permanente. Precificação de serviço ou de entrada gratuita pode tornar o componente da Meta zero; 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 estar atrasados ou incompletos. Veja 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
- Mensagens de serviço: os nove tipos de conteúdo de formato livre e o que cada um aceita
- Recebendo mensagens WhatsApp: mensagens recebidas, mídia e o webhook whatsapp.received
- IDs de usuário com escopo de negócio: endereçando um contato que entrou em contato sem número de telefone
- Templates: navegue pelo catálogo e leia as variáveis de um template
- Enviando para um grupo WhatsApp: endereçando um grupo e lendo seus recibos por participante
- Idempotência: novas tentativas seguras com o header Idempotency-Key
Recursos relacionados
Continue com a documentação, guias e exemplos sobre este tópico. Os recursos estão em inglês.
Assista ao guiaConnecting WhatsApp to Bird: from buying a number to a live channelEntenda o conceitoWhat is the 24-hour customer service window on WhatsApp?Use a ferramentaWhatsApp message builderExplore a funcionalidadeWhatsApp
Experimente na prática e obtenha um resumo de implementação