AvisosDashboardNo ar
Avisos: o botão abre o painel já com você dentro
Os avisos de pagamento e de limite — número extra, cobrança, suspensão, saldo, linhas — agora trazem um botão que abre o painel já logado, direto na tela do assunto. Sem digitar senha nem código.- Vale para o proprietário do workspace, no e-mail e no WhatsApp dele. Outros destinatários do aviso continuam recebendo o botão comum, que pede login.
- Antes de entrar, a tela mostra em qual conta e em qual workspace você vai entrar. Só o seu clique em Continuar abre a sessão.
- O link vale por 24 horas e funciona uma única vez. Depois disso, o mesmo botão leva ao login normal e, feito o login, à mesma tela.
- Não encaminhe o aviso: quem tiver o link dentro do prazo entra na sua conta.
- No e-mail, vale para todos os avisos. No WhatsApp, vale para quase todos; os que faltam passam a abrir logado à medida que a Meta aprova os novos modelos de mensagem.
NúmerosPagamentosAPIMCPDashboardNo ar
Números: o número extra é cobrado quando conecta pela primeira vez
Criar um número ou gerar um link de pareamento não cobra mais nada, em nenhum plano. Um número extra — um número acima da capacidade que o seu workspace já pagou — agora é cobrado quando o número conecta pela primeira vez, por qualquer caminho de conexão: o painel, a API, o MCP, um link de pareamento remoto ou o Embedded Signup da Meta. Um número que nunca conectou não conta na sua capacidade nem na sua cobrança mensal. Criar um número continua sendo recusado com402 (INSUFFICIENT_FUNDS ou PLAN_NUMBER_LIMIT_REACHED, com o mesmo corpo de antes) quando o próximo número seria um número extra e o workspace não tem saldo na carteira para cobri-lo nem cartão salvo. Números extras- Se a cobrança é recusada, o número continua conectado, mas o envio por ele fica bloqueado.
POST /v1/messages/sendresponde409 NUMBER_BLOCKEDcomreason: "extra_charge_unpaid", e uma mensagem que já estava na fila falha e aparece comNUMBER_BLOCKEDnos logs. Se a cobrança apenas não pôde ser concluída — o processador de pagamento estava fora do ar — nada é bloqueado, e ela é retentada automaticamente a cada 15 minutos. - O que remove o bloqueio: uma recarga da carteira ou um cartão salvo (os dois retentam a cobrança automaticamente), o botão Pagar agora na página Números do painel (só o proprietário do workspace), ou o novo
POST /v1/numbers/{id}/pay. - Depois de 7 dias sem pagamento, um número conectado por QR Code é desconectado e deixa de contar. Um lembrete é enviado 2 dias antes. Conectá-lo de novo é uma nova primeira conexão, cobrada de novo. Um número da Meta Cloud API nunca é desconectado: ele fica bloqueado para envio até o pagamento.
- Campos novos em
GET /v1/numberseGET /v1/numbers/{id}:extraChargeUnpaidSinceeextraChargeDisconnectAt, ISO 8601 ounull. Eles ficam no nível de cima do número, fora dehealth, e este bloqueio nunca aparece emhealth.code. - O plano Free nunca compra um número extra sozinho. Um workspace no Free não é convertido nem cobrado por conectar um número: criar ou parear um número acima da cota do plano responde
402 PLAN_NUMBER_LIMIT_REACHED, qualquer que seja o saldo da carteira, até você comprar o número extra por conta própria (com confirmação) ou subir de plano. Plano Free - MCP: ferramenta nova
numbers_pay.
MensagensMídiaAPIIncompatívelNo ar
Mensagens: voice junto com templateId é recusado
Enviar mediaType: "voice" junto com templateId no POST /v1/messages/send agora é recusado com 400, como já acontece com audio. Antes, a requisição era aceita e a mídia era ignorada em silêncio. Para enviar uma nota de voz, use um envio direto de mídia separado (media + mediaType: "voice", sem templateId). Áudio e nota de vozLinhasWebhooksAPINo ar
Linhas: verificação do titular pela API, a gravação da ligação pela API e o motivo da devolução
- Novo: verificação do titular pela API —
GETePOST /v1/phone-lines/verification,POST …/contact/{channel}/confirm,POST …/contact/{channel}/sendePOST …/document. Envie o CNPJ ou CPF, um e-mail de contato, um número de WhatsApp e o documento como data URI em base64 (PDF, JPEG, PNG ou WEBP, até 10 MB); confirme os códigos de contato; leia ostatus(NONE,PENDING_CONTACT,IN_REVIEW,APPROVED,NEEDS_DOCUMENTouREJECTED) e ocanPurchase. O número do documento e os contatos voltam mascarados. Só o Proprietário (phone_lines:purchase); o envio dos dados exigeIdempotency-Key; no máximo 10 envios por hora por workspace (429 RATE_LIMITEDcomRetry-After). A verificação deixou de ser só do painel, e é a mesma verificação do painel. - Novo:
GET /v1/phone-lines/activations/{activationId}/audiobaixa a gravação da ligação de um pedido de código, com a mesma chave e a mesma permissão da consulta do pedido (phone_lines:read), enviada comCache-Control: private, no-store. Um pedido sem gravação (hasAudio: false), ou de uma linha devolvida ou cancelada, responde404 ACTIVATION_NOT_FOUND. Ouvir a gravação deixou de ser só do painel. - Webhooks:
audioUrlemphone_line.audio_received,phone_line.code_receivedephone_line.code_failed— a URL absoluta dessa rota, ounullquando o pedido não tem gravação. Ela não contém credencial: busque-a com a sua chave com escopo de tenant. - Webhooks:
phone_line.returnedtrazreason—unpaid,canceled_by_customerouverification_rejected. - Uma verificação recusada desfaz as compras feitas durante a análise. 3 dias depois da recusa, as linhas compradas durante a análise são devolvidas à operadora (
reason: "verification_rejected"), e toda cobrança que elas pagaram — inclusive a parte paga no cartão — é estornada para a carteira. Até lá elas continuam funcionando e não são renovadas. As linhas compradas sob uma verificação anterior já aprovada não são afetadas. Veja verificação do titular. - Uma linha que sai do seu workspace tem o histórico de códigos apagado — quando é devolvida, ou quando uma linha cancelada chega a
CANCELED.GET /v1/phone-lines/{id}/activationscontinua listando os pedidos dela, comcode: nullehasAudio: false, e as gravações respondem404. - No painel, Pagar … agora / Pagar … e reativar, na página de uma linha, agora cobram só aquela linha (Pagar tudo, na lista de linhas, continua cobrando todas as linhas em atraso).
MensagensMídiaDocs
Correção da doc: audio é arquivo de áudio em números oficiais — use voice para nota de voz
A doc dizia que mediaType: "audio" é sempre entregue como nota de voz (PTT). Isso só vale para números não oficiais (Pilot Status web). Em números oficiais (Meta Cloud API), audio chega como arquivo de áudio — um player, não uma nota de voz. Nada mudou no produto hoje — é a doc alcançando o que ele já fazia.- Para enviar nota de voz em qualquer número, use
mediaType: "voice". Useaudiosó quando quiser um arquivo de áudio em um número oficial. OPLAYEDsó é informado em números oficiais e só para notas de voz: um envioaudionesses números não chega a ele, e em números não oficiais uma nota de voz ouvida aparece comoREAD. - O áudio nem sempre é convertido para Ogg/Opus. A entrada de 2026-07-29, mais abaixo, dizia que os dois tipos são normalizados para OGG/Opus mono. Em números oficiais, o áudio é convertido para AAC em MP4 (
audio/mp4) antes de ir para a Meta; em números não oficiais, é convertido para Ogg/Opus. - Legenda em
voicenão é recusada. A doc dizia que ela retorna400.audiocom legenda retorna;voicecom legenda é aceito, mas a legenda é descartada em números não oficiais e sua exibição em números oficiais não é garantida — não envie legenda. - Números oficiais mostram, sim, um indicador antes de um envio de áudio: “digitando…”, quando a conversa tem uma mensagem recente do contato. Números não oficiais mostram “gravando áudio…”. O guia de mídia dizia que nenhum indicador aparecia em números oficiais.
LinhasWebhooksAPINo ar
Linhas ganham nome
- Dê um nome à linha no painel (Editar nome na página da linha), com até 60 caracteres, como você dá nome a um número de WhatsApp. Em branco, o nome é apagado e a linha volta a aparecer pelo número. No painel, o nome é o título da linha e também identifica as cobranças e os recibos dela.
GET /v1/phone-lineseGET /v1/phone-lines/{id}trazem o campo novoname— o nome dado, ou o número formatado ((11) 4863-7200, igual adisplay) quando a linha não tem nome. Nunca vem vazio. A linha devolvida porDELETE /v1/phone-lines/{id}também o traz.- Todo webhook
phone_line.*sobre uma linha trazname— todos os eventos, menosphone_line.verification_updated.phone_line.purchasedsempre traz o número formatado, porque a linha é comprada sem nome. - Dar nome é só pelo painel: não há rota na API para isso, e
POST /v1/phone-linescontinua aceitando apenasnumbers.
LinhasWebhooksAPINo ar
Linhas: cancelar a espera, o código é lido de novo a partir da gravação, e todo pedido de código termina com um evento
- Novo:
POST /v1/phone-lines/{id}/activations/{activationId}/cancel. Encerra um pedido de código que ainda está emWAITING, para você pedir um novo código na hora, em vez de receber409 ACTIVATION_IN_PROGRESSaté a janela de 5 minutos fechar. O pedido passa aTIMED_OUTcomfailureReason: "canceled_by_customer". Uma ligação que já foi capturada nunca é descartada: nesse caso o cancelamento responde409 ACTIVATION_NOT_WAITING. O painel tem o mesmo botão Cancelar espera. - Uma transcrição que falha é refeita a partir da gravação armazenada, até 3 tentativas no total, então um pedido pode ficar em
CAPTUREDcomfailureReason: nullpor alguns minutos;transcription_failedagora significa que as 3 tentativas falharam. - Uma ligação nos últimos segundos da janela não se perde mais: a verificação logo depois do
pollDeadlinelê a linha uma última vez. - Webhooks: novos eventos
phone_line.audio_received(a ligação foi capturada e armazenada) ephone_line.code_failed(o pedido terminou sem código, comreason).phone_line.code_receivedagora é enviado uma vez para todo pedido aceito — com o código, ou comcode: nulle um novo campofailureReasonquando terminou sem código (nenhuma ligação na janela, espera cancelada, falha na transcrição, ou gravação que não pôde ser baixada ou armazenada). Até hoje ele só saía depois de uma transcrição concluída. Webhooks inscritos em todos os eventos ("*") recebem os eventos novos; webhooks com uma lista explícita só os recebem depois que você os incluir. - Os webhooks das linhas no painel ganharam um nome opcional (até 100 caracteres; em branco, usa o host da URL) e podem ser editados — nome, URL e eventos — sem mudar o segredo de assinatura. Uma URL que aponte para um endereço interno ou de rede privada é recusada ao salvar (
WEBHOOK_URL_REFUSED), em vez de ser aceita e depois falhar em toda entrega.
MensagensMídiaAPINo ar
URLs de mídia e as camadas Evolution: endereços internos são recusados
POST /v1/messages/sendcom uma URL demediaque aponte para um endereço interno ou de rede privada — ou que redirecione para um — agora falha comMEDIA_DOWNLOAD_FAILEDantes de qualquer requisição, em qualquer tipo de número, com oerrorMessagecomeçando porMídia recusada:. Não há nova tentativa. URLs públicas não são afetadas.- As camadas Evolution V2 e Evolution GO agora recusam, com
400, uma requisição cuja URL de mídia, foto, link pré-visualizado, webhook ou integração aponte para um endereço interno ou de rede privada. Nada é encaminhado.
NúmerosMensagensAPINo ar
Números: número Meta reconectado depois de excluído continua a funcionar — e o que não está registrado avisa
Reconectar o mesmo número Meta não o deixa mais sem conseguir enviar. Registrar um número na Cloud API define nele, na Meta, um PIN de verificação em duas etapas, e até hoje esse PIN vivia só no registro do número. Excluir o número definitivamente e conectá-lo de novo fazia o novo registro usar outro PIN; a Meta recusava (133005), o número parecia conectado e todo envio falhava com 133010 “Account does not exist in Cloud API”. Agora o PIN fica guardado por número de telefone da Meta e é reaproveitado na reconexão.Envios recusados por esse motivo agora vêm com errorCode: META_PHONE_NOT_REGISTERED em vez do genérico META_SEND_FAILED, com uma mensagem que manda registrar o número de novo no painel.health.code: meta_phone_not_registered marca um número Meta cujo status de telefone é DISCONNECTED, em GET /v1/numbers e GET /v1/numbers/{id}/status, e o painel mostra o selo “Não registrado na Cloud API”. A linha continua sendable: true de propósito: a fila não é pausada, e cada envio devolve o erro explícito da Meta. Veja health.code.No painel:- Registrar na Cloud API pede o PIN quando a Meta diz que ele não confere, em vez de terminar num erro sem onde digitá-lo.
- Excluir definitivamente agora mostra o que será perdido — quantas conversas e mensagens, e os outros dados ligados ao número — antes de você digitar o número para confirmar.
- Reconectar um número Meta que ainda está na lixeira agora passa pelo Restaurar — a mesma janela de 30 dias e a mesma cobrança do Restaurar — e traz o número de volta com conversas, chaves e configurações. A correspondência é pelo número de telefone da Meta, no mesmo workspace.
NúmerosMensagensAPINo ar
Números não oficiais: reconectar o mesmo número volta a funcionar
- Desconectar e reconectar o MESMO número não oficial podia não ser registrado. Quando isso acontecia, o
number.connectednão era enviado, o painel continuava mostrando o número desconectado e com o envio pausado, ePOST /v1/messages/sendrespondia409 NUMBER_BLOCKEDaté alguém clicar em “Retomar envio”. Corrigido: a reconexão agora é registrada, onumber.connectedé enviado e o envio é liberado assim que a conexão volta, sem clique. - O
409 NUMBER_BLOCKEDcomreason: "all_connections_down"agora diz o que significa: o número está desconectado há mais de 60 minutos e precisa ser reconectado antes de enviar. Antes dizia que o WhatsApp estava recusando os envios.code,reasone o status HTTP não mudaram. Códigos de erro POST /v1/messages/{id}/resendagora responde o mesmo409 NUMBER_BLOCKEDdePOST /v1/messages/sendquando o número não pode enviar. Antes respondia um500genérico.- Mensagens que expiram com o número desconectado há mais de 60 minutos agora dizem isso no
error. OerrorCodecontinuaEXPIRED_WHILE_NUMBER_BLOCKED.
MensagensMídiaDocs
Correção da doc: mídia em base64 funciona em números não oficiais
A doc dizia que números não oficiais (Pilot Status web) não aceitammedia em base64 no POST /v1/messages/send. Aceitam, desde 2026-08-24. Nada mudou no produto hoje — é a doc alcançando o que ele já fazia. Em números não oficiais, a Pilot Status sobe o arquivo decodificado para o próprio armazenamento e envia uma URL ao provedor; se esse upload falhar, a mensagem falha com MEDIA_REHOST_FAILED. Os tetos do base64 não mudam (50 MiB para document, 16 MiB nos demais tipos). Para figurinhas em números não oficiais, use uma URL http(s) pública. Mensagens de mídiaMensagensMídiaAPINo ar
Mensagens: documento em base64 vai até 50 MiB
POST /v1/messages/send passa a aceitar media em base64 de até 50 MiB (52.428.800 bytes, decodificados) quando mediaType é document. Até agora todo payload base64 tinha teto de 16 MiB, qualquer que fosse o tipo, e um documento de 20 MB enviado inline era recusado, embora a Meta aceite documento de até 100 MB. Os demais tipos — image, video, audio, voice, sticker — e uma media enviada sem mediaType continuam com teto de 16 MiB (16.777.216 bytes).A recusa mantém o formato. Acima do teto a requisição responde 400 Validation error antes de qualquer coisa entrar na fila, e details.fieldErrors.media informa o limite que valeu:media base64 payload exceeds the 16777216 byte limit.Mídia enviada por URL não muda. Uma URL http(s) pública não segue esses tetos; em números Meta Cloud API valem os tetos da própria Meta (documento 100 MB, vídeo e áudio 16 MB, imagem 5 MB), e um arquivo acima deles falha depois do 202 com META_MEDIA_TOO_LARGE. Para documento acima de 50 MiB, envie uma URL. Enviar mensagemNúmerosAPIMCPSDKNo ar
Números: excluir é reversível por 30 dias — e a exclusão permanente agora está documentada
DELETE /v1/numbers/{id} é um soft delete de 30 dias, desde agosto: o aparelho é desconectado na hora, e a resposta traz restorableUntil. Dois endpoints completam o fluxo, e a documentação não citava nenhum deles: POST /v1/numbers/{id}/restore traz o número de volta dentro da janela, e DELETE /v1/numbers/{id}/purge exclui de forma permanente, na hora. Purgue antes de criar o mesmo telefone de novo — enquanto o número está na janela, POST /v1/numbers responde 409 NUMBER_SOFT_DELETED.Cobrança: no Pay As You Go, número excluído não é cobrado na próxima virada, purgado ou não. Nos outros planos, excluir não reduz a capacidade paga — para isso, agende uma redução. Restaurar paga como criar: com a capacidade paga cheia, o restore compra um número extra, com prorata; se isso não puder ser pago, responde 422 INSUFFICIENT_FUNDS e o número continua excluído.MCP: ferramenta nova numbers_purge (só OWNER), ao lado de numbers_restore. SDK Python 1.8.0: numbers.purge() e numbers.restore(), e numbers.delete() passa a tipar a resposta real (ok, restorableUntil).ChatwootMensagensNo ar
Chatwoot (espelho API): mídia de números não oficiais chega com o anexo — e com nome de arquivo
Imagens, áudios, vídeos e documentos recebidos em números não oficiais (web/QR) voltam a chegar ao Chatwoot com o anexo. No espelho API (Modelo A), desde o fim de agosto, eles podiam chegar como uma bolha vazia: o espelho postava a mensagem antes de a mídia estar guardada. Agora ele espera a mídia e posta a bolha com ela, em geral em poucos segundos. Se a mídia ainda não estiver disponível depois de 20 segundos, a bolha diz[mídia] com um link que abre o arquivo assim que ele é guardado. A mídia recebida antes desta mudança fica como está no Chatwoot. O que o atendente vêUm anexo que chega sem nome de arquivo agora recebe um. Fotos, áudios de voz, vídeos e figurinhas costumam vir sem nome e chegavam ao Chatwoot como attachment, sem extensão. Agora recebem o nome do tipo e o id da mensagem — imagem-3EB03F18.jpg, audio-3EB0FFE7.ogg, figurinha-3EB0A1B2.webp —, e o download abre no aplicativo certo. Um arquivo que traz nome próprio continua com ele.ChatwootMensagensAPINo ar
Chatwoot: resposta recusada mostra o motivo, mensagem da API segue a conversa aberta, texto não se perde atrás do “não suportada”
Uma resposta do Chatwoot que a Pilot Status recusa agora fica marcada como falha no Chatwoot, com o motivo na bolha e uma nota privada na conversa — antes a bolha parecia enviada. Vale para toda recusa de resposta de atendente: o WhatsApp recusando envios do número, cobrança suspensa, janela de 24h fechada,/template inexistente, anexo sem URL pública, mensagem vazia, conversa sem destino. O que o atendente vêUm código novo, NUMBER_BLOCKED, nomeia a resposta do Chatwoot recusada porque o WhatsApp está recusando envios do número (banido, restrito). Antes ela era retentada quatro vezes e registrada como CHATWOOT_SEND_EXCEPTION em cada tentativa; agora é registrada uma vez e não é retentada. É o mesmo código que o POST /v1/messages/send responde com 409 nesse estado. Códigos de erro dos logsInbox nativa Cloud (Modelo B): mensagens enviadas pela API da Pilot Status caem na conversa aberta do contato. O Chatwoot abre uma conversa nova a cada vez que a anterior é resolvida, e as mensagens da API continuavam indo para a antiga, já resolvida. Sem conversa aberta, a Pilot Status agora abre uma, e a resposta do cliente entra nela.Inbox nativa Cloud (Modelo B): a mensagem real não se perde mais atrás do “Esta mensagem não é suportada”. Em números em coexistência, o WhatsApp às vezes entrega um aviso provisório (131060) antes da mensagem real, com o mesmo id, e o Chatwoot guardava o aviso e descartava o texto. A Pilot Status agora segura o aviso por alguns segundos e o descarta quando a mensagem real chega. Quando o WhatsApp nunca entrega o conteúdo para a API, o aviso continua aparecendo — a mensagem existe só no aplicativo do WhatsApp.Envio para número sem o código do país não divide mais a conversa na inbox e no GET /v1/conversations: em números oficiais, a conversa segue o número que o WhatsApp confirma para o envio. Continue enviando em E.164 (com o código do país).MensagensAPINo ar
errorCode no detalhe da mensagem
GET /v1/messages/{messageId} agora devolve o errorCode — o mesmo valor que GET /v1/messages devolve para a mesma mensagem, pela mesma derivação. Até agora o detalhe trazia só o errorMessage, cujo texto não é contrato. Ramifique pelo errorCode; ele é null quando não há erro. No detalhe, é aditivo: nenhum campo existente muda.Códigos de Erro do Log agora lista todos os valores que o errorCode pode ter — inclusive as recusas em números web e o conjunto completo da Meta Cloud API — com o código numérico da Meta por trás de cada código META_*, para você ligá-lo ao statuses[].errors[].code do webhook nativo.Nos dois endpoints e nos Logs, dois códigos novos, BILLING_SUSPENDED e NUMBER_DELETED_BEFORE_SEND, dão nome a falhas que chegavam como SEND_FAILED, e um shadowban web (erro 463) agora aparece como WEB_SHADOWBAN em vez de SEND_FAILED. O PLAYED se junta a DELIVERED, READ e CANCELED como status cujo errorCode é sempre null.Mais 21 códigos agora dão nome próprio a falhas: expirações do reconciliador com o número bloqueado ou desconectado, respostas de atendentes no Chatwoot, recusas de composição de template (lista, carrossel, Flow), falhas de mídia e de card de pedido em números web, e recusas da plataforma em números oficiais antes de o envio chegar à Meta — lista completa. Elas chegavam como SEND_FAILED (META_SEND_FAILED nas recusas de número oficial), e algumas como um código deduzido do texto do erro de origem, como DELIVERY_TIMEOUT. Se você ramifica pelo SEND_FAILED, espere estes códigos mais específicos no lugar, inclusive em mensagens antigas: o código é calculado quando a mensagem é lida, não quando ela falhou.Em números oficiais, um envio que a Meta recusa na própria requisição agora recebe o mesmo código que o webhook de status dá para aquele número da Meta. O 131048 é META_RATE_LIMITED (dizia META_NO_CONVERSATION), o 131052 e o 131053 são META_MEDIA_DOWNLOAD_ERROR e META_MEDIA_UPLOAD_ERROR, o 4 e o 80007 são META_RATE_LIMITED, o 100 é META_INVALID_PARAM, o 131000 é META_GENERIC_ERROR, e o 190 é META_TOKEN_INVALID nos dois caminhos. Três códigos do envio passam a ser públicos: META_TEMPLATE_INVALID_BUTTON_URL, META_NUMBER_INFO_NEEDED e META_CONTENT_FLAGGED. Vale só para mensagens que falharem depois do lançamento — as antigas mantêm o código que receberam, exceto os quatro códigos antigos abaixo. A página também mapeia cada código do webhook message.failed para o errorCode da API da mesma mensagem.Quatro códigos que o caminho do envio não grava mais agora são lidos com o código de hoje também em mensagens antigas, porque cada um vinha de um único número da Meta: META_SPAM_RATE_LIMITED → META_RATE_LIMITED, META_TEMPLATE_BAD_PARAM → META_MEDIA_DOWNLOAD_ERROR, META_BAD_REQUEST → META_INVALID_PARAM, META_UPLOAD_SESSION_INVALID → META_GENERIC_ERROR. Um META_NO_CONVERSATION ou META_TEMPLATE_NOT_APPROVED antigo fica como foi gravado, porque é ambíguo.Mais onze códigos dão nome a falhas que o worker gravava só como texto livre, e que apareciam como SEND_FAILED: conexão e credenciais, download e conversão de mídia, composição da mensagem, e PROVIDER_UNREACHABLE para um provedor que não pôde ser alcançado em nenhuma tentativa — lista completa. Algumas falhas também saem de DELIVERY_TIMEOUT ou SEND_FAILED para um código mais preciso. Elas mudam o que é gravado, então só mensagens que falharem depois do lançamento as recebem.Um código novo, META_DELIVERY_UNKNOWN, marca um envio de número oficial que pode ter chegado à Meta mas não recebeu id de mensagem de volta: não se sabe se entregou, então não reenvie às cegas — a linha do código diz como conferir. Um upload de mídia para a Meta que não devolveu id da mídia agora aparece como META_MEDIA_UPLOAD_ERROR. Os dois valem só para mensagens que falharem depois do lançamento.TemplatesMensagensAPIChatwootNo ar
Botões de URL dinâmica de templates criados no painel da Meta podem ser enviados
Um template criado no WhatsApp Manager da Meta com placeholders posicionais numera a variável do seu botão de URL dinâmica por conta própria: emhttps://ctlnk.io/{{1}}, esse {{1}} não é o {{1}} do body. Até agora não havia como preenchê-la — o envio era aceito com 202 e depois falhava com o 131008 da Meta (“Button at index 0 of type Url requires a parameter”).Mande como button_<N> em variables, em que N é a posição do botão na lista de botões do template, começando em 1 — todos os botões contam, inclusive os estáticos. Vai só a parte variável, não a URL inteira:GET /v1/templates e GET /v1/templates/{id} listam a chave em variables — ["1", "2", "3", "button_1"] para o template acima.Valor ausente é recusado na hora. Um envio sem button_<N>, ou com valor vazio, agora responde 400 MISSING_TEMPLATE_VARIABLES (ou INVALID_TEMPLATE_VARIABLE_VALUE) nomeando a chave, em vez de 202 e uma mensagem FAILED depois.Placeholders nomeados não mudam: um botão criado no Pilot Status como https://loja.com/promo/{{link}} continua sendo preenchido por variables.link. DetalhesBotões de copiar código em números oficiais recebem o código a cada envio. Para a Meta, o código digitado num botão de copiar código na criação é só um exemplo; o código que o cliente copia é um parâmetro obrigatório de cada envio. Até agora o Pilot Status não mandava nenhum para um botão sem {{variável}}, e a Meta recusava o envio. Num template aprovado pela Meta (source: "META"), esse botão agora também recebe o código como button_<N> — "button_2": "BLACKFRIDAY" para os botões [QUICK_REPLY, COPY_CODE] —, o GET lista a chave, e um envio sem ela é recusado com 400. Nos números não oficiais o código também é por envio, pela {{variável}} do botão. DetalhesChatwoot: o /template preenche variáveis de botão. O comando do compositor aceita button_<N>=valor entre os argumentos sem consumir um número posicional — /template link_pagamento_pix Maria, R$ 10, 21/09/2026, button_1=SdjLn — e templates enviados com os parâmetros estruturados do seletor de templates de WhatsApp do Chatwoot (processed_params com body e buttons) agora chegam ao envio, os parâmetros do body como variáveis e os de botão, pela posição, como button_<N>. DetalhesTemplates com variáveis nomeadas criados no painel da Meta passam a receber edições. Um template criado no WhatsApp Manager da Meta com parâmetros nomeados ({{first_name}}) ficava com a cópia que o Pilot Status importou da primeira vez: uma edição feita depois no painel da Meta — um botão novo, um texto alterado — nunca chegava, e o envio podia então ser recusado pela Meta. A sincronização de templates agora traz essas edições. Templates criados no Pilot Status mantêm os nomes das variáveis, como antes. As chaves button_<N> em variables agora também saem na ordem dos botões.WebhooksIncompatívelNo ar
Números oficiais não recebem mais eventos canônicos message.*
Em número conectado pela API oficial da Meta, a família canônica message.* não é mais despachada para os seus webhooks — nem message.sent, nem message.failed, nenhum. Esses números recebem o envelope nativo da Meta, que é onde o resultado de um envio sempre esteve: value.statuses[], com o status e, em caso de falha, o errors[].Isto é uma mudança, não um esclarecimento. Até hoje o message.sent e o message.failed chegavam a webhooks de números oficiais que tinham assinado "*" — 3.310 entregas de sent para 19 webhooks nos 7 dias anteriores à mudança. Se o seu receptor depende desses dois eventos num número oficial, ele parou de ouvi-los hoje.O que usar no lugar, por ordem de preferência: leia o value.statuses[] do envelope nativo que você já recebe; ou consulte GET /v1/messages?status=FAILED, que funciona nos dois tipos de conexão porque a linha da mensagem chega a FAILED de qualquer forma — inclusive para falha reportada pela própria Meta.Números não oficiais não mudam: o message.* é despachado neles exatamente como antes. E em números oficiais as famílias number.*, call.* e flow.response_received continuam chegando. DetalhesMensagensAPINo ar
Reenviar uma mensagem que falhou sem guardar o payload
POST /v1/messages/{id}/resend envia novamente a requisição guardada de uma mensagem FAILED ou CANCELED. Você não precisa mais manter o corpo original do seu lado para se recuperar de uma falha.Ele cria uma mensagem nova, com id e correlationId próprios, e deixa a original intacta — status, datas e id da mensagem no provedor continuam legíveis, então as duas tentativas seguem distinguíveis. A resposta tem o mesmo formato que POST /v1/messages/send devolve, mais o originalMessageId. Em template com botão de Flow ela carrega um flowToken novo, para a resposta que voltar ser atribuível a esta tentativa e não à primeira.O identificador é o mesmo que GET /v1/messages/{id} aceita: id interno, correlationId ou o wamid do provedor.Consome cota, como qualquer envio, e exige messages:resend (ADMIN/OWNER) — deliberadamente mais alta que messages:send, porque um reenvio gasta cota num envio já pago uma vez e pode entregar duas vezes.Antes de automatizar retentativa: uma mensagem em FAILED já foi retentada pela plataforma — 10 tentativas a cada 30s, um reconciliador, e uma checagem de entrega aos 30 e aos 60 minutos. FAILED quer dizer que desistimos, então reenviar na hora normalmente falha pela mesma causa. Leia o errorMessage antes e reenvie quando a causa estiver corrigida. SENT e QUEUED são recusados com 409 MESSAGE_NOT_RESENDABLE pelo mesmo motivo: ainda estão em voo, e reenviar dá dois remetentes para uma mensagem. DetalhesDashboardNumbersHistoryNo ar
O card do número avisa quando o histórico do WhatsApp se perdeu
No ar em produção desde hoje.Aviso “Histórico não importado” na página Números. Quando o histórico do aparelho de um número Meta chegou com a importação de histórico desligada, ou a Meta recusou enviá-lo de novo, o card do número mostra um selo âmbar. Abra o card para ver a data, a quantidade de eventos e o que isso significa. É a mesma informação dohistoryImport no GET /v1/numbers/{id}. DetalhesO botão “Sincronizar histórico” voltou. Ele não aparecia para números em coexistência desde 30 de junho. Se a Meta já enviou o histórico do número, ela recusa o novo pedido, e o aviso passa a aparecer no card na hora, em vez de só no próximo carregamento da página.Também corrigido: alguns textos mostravam o apóstrofo dobrado (“I”m”), e a ajuda do Pix nos formulários de template aparecia em inglês para quem usa português e espanhol.NumbersHistoryAPINo ar
O pareamento remoto aceita settings na Meta, e o número diz o que aconteceu com o histórico
No ar em produção desde hoje.O POST /v1/numbers/remote-pairing respeita settings com provider=META. Antes ignorava o bloco sem avisar, então um link Meta não conseguia decidir o histórico na criação. Agora ele é gravado na linha provisória em que a conexão vai cair, e o 201 devolve o numberId dessa linha junto com os settings com que ela foi criada. Se você tirava o id de dentro do token do link, leia-o do corpo. DetalheshistoryImport no GET /v1/numbers/{id}. discarded diz que o histórico do aparelho chegou com historyImportEnabled em false e foi descartado; syncRefused diz que a Meta recusou enviá-lo de novo. Os dois são definitivos — a Meta envia o histórico de um número uma vez só, e apagar e reconectar não repete o envio. DetalhesA mídia do histórico passa a ser importada. A Meta a envia em entregas separadas, depois das conversas; essas entregas eram ignoradas, então as mensagens de mídia do histórico apareciam vazias. A mídia que a Meta nunca enviou continua como placeholder.Também: um pedido de histórico que a Meta recusa por já ter sido feito passa a responder 422 HISTORY_SYNC_LIMIT_REACHED no botão de sincronizar do painel, em vez de 500.WebhooksAPINo ar
As entregas de webhook passam a ser assinadas, e a saúde do número é assinável
No ar em produção desde hoje. As duas coisas mudam respostas que esta própria página deu mais cedo no mesmo dia.A assinatura funciona agora. Criar um webhook cunha um segredo e devolve-o em claro naquele201 — a única resposta na vida de um segredo que o carrega. As entregas passam a incluir x-pilot-status-signature, o HMAC-SHA256 do corpo bruto em hexadecimal.Webhooks criados antes de hoje não têm segredo e continuam sem assinatura: ligar verificação por baixo de uma integração viva é, por si só, uma interrupção. O POST /v1/webhooks/{id}/rotate-secret liga num deles, e é também a única forma de recuperar um segredo perdido. Leia o hasSecret para distinguir os dois casos antes de decidir se o seu receptor pode recusar entrega sem assinatura. Detalhesnumber.health_* já não precisa de "*". A família — number.health_degraded, number.health_blocked, number.health_shadowban, number.recovered, number.inbound_stalled, number.inbound_recovered — está no seletor de eventos. Ela sempre foi despachada, mas só a quem tinha assinado tudo; assinar exatamente o que se queria significava não receber sinal de saúde nenhum. Num número Meta isso é a história inteira, porque number.disconnected não dispara lá. DetalhesIdempotency-Key em número Meta. O envelope nativo passa a levar o cabeçalho, chaveado <field>:<wamid>:<type> — deliberadamente não só o wamid, porque a Meta entrega o mesmo wamid duas vezes com conteúdos diferentes. O corpo do envelope fica inalterado: a chave viaja só no cabeçalho, então o que você recebe continua byte a byte o que a Meta mandou.Também no ar: origin e metaPricing no GET /v1/messages/{id} (a categoria que a Meta cobrou, já não inferida pelo template), e sendable + health no GET /v1/numbers/{id}/status de números Meta — aditivos, o state de lá continua a responder OPEN por desenho.WebhooksNúmerosDocs
Quatro coisas que a doc dizia e que a plataforma não faz
Correções, todas elas estruturais para quem está construindo uma integração agora. Nada mudou no produto — é a doc alcançando o que ele já fazia.1 — Não dá para definir um segredo de assinatura. Em lugar nenhum. A doc de webhooks dizia que ele era configurado na página Webhooks do painel. Não é: aquele formulário temname, url e events, e a API também. A coluna secret existe e o caminho de entrega a respeita, mas nenhuma superfície escreve nela — então x-pilot-status-signature não é enviado hoje. Não construa um receptor que rejeite entrega sem assinatura; ele rejeitaria tudo. Detalhes2 — O Idempotency-Key não vem em toda entrega. Ele é derivado de um id de mensagem no payload, então um corpo que não tem nenhum não recebe cabeçalho nem campo idempotencyKey. Isso inclui todo envelope nativo da Meta Cloud API ({ object, entry }) e as famílias number.* — ou seja, todo o tráfego de conversa de um número Meta. Detalhes3 — Num número Meta, o GET /v1/numbers/{id}/status não é checagem de saúde. Uma vez que o número tenha um phone id da Meta, o state ali é sempre OPEN — ele nunca consulta a saúde. Um número RESTRICTED, ou com qualidade caída para RED, responde OPEN ali enquanto GET /v1/numbers e GET /v1/numbers/{id} respondem DISCONNECTED para o mesmo número no mesmo instante. Leia health.sendable para “ele consegue enviar”. Detalhes4 — Dois links de pareamento, dois TTLs diferentes. A sessão de Embedded Signup META dura 30 minutos; o token de QR PILOT_STATUS dura 24 horas ou até conectar. A doc citava as 24h para os dois. Leia o expiresAt do 201 em vez de fixar qualquer um no código. DetalhesDocumentados na mesma passagem: o bloco health completo (portfolioMessagingLimitTier, code, reason e todos os valores que cada um assume), onde a categoria cobrada de fato mora (value.statuses[].pricing, não no detalhe da mensagem), o bloco referral de CTWA e a armadilha do wamid repetido, os eventos de saúde do número que só chegam a quem assina "*", as duas chaves de array sob as quais um eco pode chegar, e a janela real do histórico de coexistência — 30 dias, não seis meses.NúmerosAPI
O platformType nunca diz COEXISTENCE — e isso não é defeito
Um número em coexistência — que continua ativo no app WhatsApp Business de um aparelho e conectado à Cloud API — reporta "platformType": "CLOUD_API". Porque ele está na Cloud API. É isso que coexistência significa, e o conjunto de valores da Meta para esse campo não tem COEXISTENCE; então ler esse campo para responder “este número está em coexistência?” responde não, sempre.GET /v1/numbers e GET /v1/numbers/{id} passam a devolver um bloco que responde de verdade:status é CONFIRMED, NO, LIKELY ou UNKNOWN. O evidence diz de onde veio a resposta, para você separar resposta de palpite — o campo is_on_biz_app da própria Meta (também espelhado em meta.phone.isOnBizApp), o que a Meta respondeu quando pedimos o histórico do aparelho, ou a marca que a Meta deixa num número que ela recusa registrar.UNKNOWN não quer dizer “não”. O sinal por trás do LIKELY é de mão única: quando aparece, vale confiar; a ausência dele não prova nada. Chame ?refresh=1 no número para buscar a resposta da própria Meta.O que a coexistência muda na prática: a Meta recusa o /register do número, então chamadas não podem ser habilitadas enquanto ele seguir assim. O envio de mensagens não é afetado.FlowsAPI
Um Flow cujo payload nomeava o Form foi publicado, abriu, e não mandou nada ao seu endpoint
Se o payload data_exchange do seu Flow referenciava valores como ${form_produto.quantidade} — o nome do próprio componente Form como prefixo —, a Meta compilou o documento, publicou, respondeu validation_errors: [] e todas as entidades voltaram AVAILABLE. Nada, em lugar nenhum, disse uma palavra.Em execução, o seu endpoint recebia isto:O prefixo é palavra-chave, não nome
Um valor de formulário é referenciado pela palavra-chave literalform. O name que você deu ao Form nunca entra na referência:${screen.<SCREEN_ID>.form.<nome do campo>} — o form literal continua lá.O que mudou
OPOST /v1/flows/{id}/json agora recusa um documento assim antes de ele chegar à Meta, e devolve a substituição já escrita:422, o mesmo do FLOW_JSON_INVALID — trate pelo code, não pelo status.Estreita de propósito, e um
200 continua não sendo veredito sobre o seu documento. A checagem dispara só quando o root de uma referência coincide com o name de um Form declarado no mesmo documento — o único caso que comprovadamente não resolve em versão nenhuma. Um root que não reconhecemos passa: a nossa ignorância não pode virar erro seu. Todo o resto continua sendo a Meta a julgar.Já que está aqui: responda 200 em erro de negócio
Qualquer resposta diferente de 200 do seu endpoint vira aquela mesma frase genérica no telefone, seja qual for o motivo. Um erro que a pessoa precisa ler vai dentro da resposta:FlowsAPI
Um Flow data_exchange não podia ser enviado — e a culpa era da tela de entrada que nós acrescentávamos
Se você enviou um Flow com "action": "data_exchange", a mensagem nunca chegou. A Meta recusava:A tela era nossa, não sua
Nada naquelas requisições estava errado. O corpo levavaflowId, cta e action — sem tela. Nós resolvíamos a primeira tela do Flow e a acrescentávamos, que é o certo num envio navigate e é a única coisa que a Meta proíbe num data_exchange: para essa ação o payload da tela de entrada não é opcional, é recusado.Medido em produção em 10 de setembro de 2026: três envios de uma conta, nenhum entregue, com a janela de 24h aberta o tempo todo.O que mudou
Deixamos de resolver e de anexar tela de entrada quando a ação édata_exchange, e o campo é descartado no último passo antes do fio mesmo que algo mais acima ainda o carregue. Um Flow data_exchange agora envia, entrega e abre — verificado ponta a ponta em 11 de setembro de 2026, INIT incluído.screen e data sempre foram exclusivos do navigate; agora a API diz isso, em vez de deixar a Meta dizer:Recusado, não descartado. Jogar fora o
data caladamente deixaria você acreditar que valores chegaram ao Flow sem nunca terem saído do nosso processo. O estado inicial de um Flow data_exchange chega pela requisição INIT que a Meta faz ao seu endpoint.flow também entrou no spec OpenAPI e no Playground, onde antes só a prosa o tinha.FlowsWebhooksAPI
Num Flow data_exchange as respostas nunca estiveram no response — o submitted é que as traz
A gente mandava você ler o data.response para saber o que o cliente digitou. Num Flow data_exchange esse campo é o lugar vazio, e sempre foi.O que o response traz de verdade ali
O response é o nfm_reply da Meta, parseado. Num Flow cujas telas são respondidas pela sua API, a submissão que fecha o formulário carrega o token e mais nada. Medido três vezes em produção em 2026-09-06, é isto por inteiro:{"observacao": "Aobaaa", "foto": [...]}. As respostas existiam — chegaram até você tela a tela, dentro das trocas cifradas — e nada aqui as guardava, então toda superfície que se oferecia para mostrar uma submissão data_exchange mostrava um token no lugar.Isto é o caso
data_exchange, não todo Flow. Um Flow simples — sem endpoint, NAVIGATE entre telas — entrega os componentes de texto livre no response, e sempre entregou. É quando a sua API responde as telas que o nfm_reply esvazia, porque as respostas foram para a sua API em vez de virem dentro dele.Novo: submitted, no evento e na superfície de leitura
O flow.response_received (dentro de data) e o GET /v1/flows/{id}/responses passam os dois a trazer submitted — as trocas que a Meta nos mandou enquanto a pessoa ainda preenchia o formulário, em ordem:screen é string ou null, o at é ISO-8601, e o data é o objeto parseado ou null.Vazio escreve-se de forma diferente nas duas superfícies, e isso é de propósito. Em GET /v1/flows/{id}/responses o submitted é [] e nunca null: o valor vem de uma busca que não responde nada quando não há trocas, e um campo posto a nada desapareceria do JSON em vez de chegar vazio. No flow.response_received é null e nunca [] — a regra que o flowMedia já segue lá, pelo motivo de aquele payload ser guardado: o redator só apaga um campo que tenha algo dentro, então um array vazio voltaria a ler-se como “havia respostas e nós as escondemos”. Nenhuma das duas grafias é prova de que a pessoa não digitou nada.Nada é truncado em silêncio
Oomitted é null ou exatamente um de três valores. Ou a entrada tem o data com omitted: null, ou tem data: null e o motivo de faltar. Não existe uma terceira forma, então um data nulo sempre tem a resposta ao lado.“Grande demais” é medido em bytes, não em caracteres. Acento e emoji custam mais de um byte cada, então o
DATA_TOO_LARGE pode cair numa resposta que parece curta.A lista é mais curta que o rastro do endpoint, e é assim que tem de ser
Só as trocasdata_exchange são capturadas — o INIT e o BACK não. O INIT devolve o estado que nós semeamos, os params mandados no envio voltando para nós, e o BACK é navegação. Nenhum dos dois é coisa que alguém digitou. Então a tela de rastro do Flow lista mais chamadas do que o submitted lista entradas. Essa diferença é o INIT e o BACK, e a lista mais curta é a lista certa.É append-only, sem deduplicação. Quem volta uma tela e submete de novo produz uma segunda entrada, legítima — as duas são reais, e o at é o que separa duas passagens pelo mesmo formulário sob um único token.Um número em RELAY_ONLY não guarda nada aqui. Não é linha apagada: não há linha. A troca continua sendo decifrada e repassada ao seu endpoint, como tudo nesse modo; o que não fica é a cópia.Guardado por 30 dias + 1
As respostas são podadas aos 30 dias, as trocas por trás delas aos 31, e o dia a mais não é arredondamento. Uma troca é capturada enquanto o cliente ainda preenche o formulário, minutos antes da submissão que cria a linha da resposta. Com 30 dias iguais as trocas expirariam primeiro, e uma resposta nos seus últimos minutos de vida publicaria umsubmitted vazio ao lado de respostas que estiveram lá o tempo todo.Veja Receber as respostas de um Flow para o campo nas duas superfícies, e Retenção de dados e modos de PII para as janelas.FlowsWebhooksAPI
O endpoint_uri não tinha quem o escrevesse em lugar nenhum do produto, e um arquivo anexado dentro de um formulário não chegava a superfície nenhuma
Dois buracos no mesmo recurso, os dois do tipo que não reporta nada.Dava para configurar tudo, menos o único campo que a Meta exige
A entrada anterior deixou o proxy dedata_exchange configurável: uma chave por número, uma URL de encaminhamento por Flow. Os dois diziam, com todas as letras, que salvar a URL não registra nada na Meta — e nada mais registrava também. Não existia quem escrevesse o endpoint_uri em superfície nenhuma: nem no painel, que mostrava FLOW_ENDPOINT_META_URI_UNKNOWN e não oferecia ação nenhuma, nem na API. Dava para ligar um Flow data_exchange de ponta a ponta e mesmo assim não conseguir publicá-lo, porque a Meta recusa o publish com “Publishing without specifying ‘endpoint_uri’ is forbidden” (o texto é da Meta, não nosso).Novo: POST /v1/flows/{id}/endpoint — o mesmo caminho da gravação, outro ato. O GET lê esse estado e o PUT / PATCH guardam o seu destino; nenhum dos três fala com a Meta. O POST escreve a URL do nosso proxy no endpoint_uri do Flow na Meta.PUT — e confirmOverwrite é o único campo aceito. Qualquer outro é 400 FLOW_UNKNOWN_FIELDS; whatsappNumberId no corpo é 400 FLOW_NUMBER_FROM_KEY, porque o número vem da credencial como em toda a /v1; corpo truncado é 400 FLOW_BODY_INVALID e nunca é lido como corpo vazio. O 200 é o mesmo estado do endpoint que o GET e o PUT já devolviam, sem campo novo e sem secret — o POST nunca cria um.É idempotente, e a verificação lê a Meta ao vivo. Um Flow que já aponta para nós responde 200 sem escrever no Graph. Ela deliberadamente não consulta a coluna metaEndpointUri, sincronizada de hora em hora, porque essa coluna é muda sobre um URI registrado na última hora — exatamente a janela em que duas pessoas ligam o mesmo Flow.Um Flow apontando para outro lugar é recusado, e a recusa nomeia o outro endpoint:409, e um dos dois corpos desta superfície que trazem uma quarta chave — o FLOW_JSON_INVALID já acrescenta validationErrors do mesmo jeito. A nossa URL deliberadamente não é ecoada — ela embute o token de endpoint do número, que é a credencial do proxy. Reenviar com {"confirmOverwrite": true} assume o Flow, e assumir tira todas as trocas desse Flow de quem responde por ele hoje: a escrita da Meta é sobrescrita pura, sem mesclagem, sem desfazer e sem aviso nenhum ao endpoint que deixa de ser chamado.O confirmOverwrite nunca é coagido. Um valor que não seja booleano é 400 FLOW_ENDPOINT_CONFIRM_INVALID em vez de leitura por veracidade — "false" é uma string verdadeira, e lê-la como consentimento tomaria conta de um endpoint vivo numa requisição cujo autor acreditava ter recusado.Um arquivo anexado dentro de um formulário expirava no CDN da Meta e nunca era nosso
UmPhotoPicker ou um DocumentPicker põe um arquivo na submissão, e ele nunca chega em response. Os bytes vão para a Meta, e a referência que chega até eles — com a chave AES que a Meta deriva por arquivo ao lado — viaja dentro do pedido data_exchange cifrado que só o proxy decifra, nunca no nfm_reply que vira o response. A Meta então apaga o arquivo depois de no máximo 20 dias, e nada aqui o decifrava, copiava ou listava: não existia decifrador nenhum no código, então o anexo simplesmente se perdia. Agora ele é decifrado, re-hospedado, e publicado nas duas superfícies e nos tipos do SDK.O flow.response_received agora traz flowMedia:GET /v1/flows/{id}/responses agora traz media em cada resposta — os mesmos arquivos, já resolvidos, mais o sizeBytes:url já vem na primeira entrega, e é um link que espera. O evento é emitido assim que a submissão é gravada e a re-hospedagem roda numa fila depois dele, então a submissão normalmente é anunciada com a transferência ainda em curso. Em vez de publicar nada durante essa janela, o url aponta para uma rota nossa: buscá-lo antes de os bytes chegarem segura a requisição enquanto a re-hospedagem termina e então entrega o arquivo, toda busca depois disso é um redirect, e uma espera que estoura responde 404 com o cabeçalho Retry-After. O valor não muda quando o arquivo chega, então dá para guardá-lo já na chegada — e o status (PENDING / STORED / FAILED) continua sendo como você sabe se os bytes já são nossos. Trate o link como opaco: busque, nunca monte um.O
url continua vindo null em três casos, e “ainda não transferiu” não é nenhum deles. Uma instalação sem segredo de assinatura configurado cai no comportamento antigo — a URL direta do objeto, assim que o status for STORED; a Meta não mandou media_id para assinarmos uma identidade em cima; ou o número é RELAY_ONLY, onde nenhum anexo é guardado.mimeType é detectado pelos bytes já decifrados, nunca pelo nome do arquivo, e nunca é text/html nem image/svg+xml — os dois executariam script na origem que os serve.O fileName nunca é null. Foto tirada na câmera não reporta nome nenhum, e aí entra o nome do próprio campo — então um fileName igual ao field pode ser o do aparelho ou o nosso, e os dois são indistinguíveis.Nenhuma das duas superfícies leva cdn_url nem encryption_metadata. A Meta deriva uma chave AES por arquivo; repassar qualquer um dos dois colocaria a chave que abre o documento do cliente dentro do seu webhook e dos seus logs.SDK de Node. FlowResponse.media e FlowResponseReceivedEventData.flowMedia estão tipados, e o parser de webhooks agora declara o flowMedia — essa linha é o ponto inteiro: z.object remove as chaves que não conhece, então um campo presente no fio e ausente do schema não dá erro, ele some, e você vê uma submissão sem anexos em vez de um erro dizendo que o SDK está atrasado. É .nullable().optional() de propósito: null é o emissor dizendo que não há nada listado, ausente é um backend anterior ao campo, e recusar o segundo recusaria todo evento vindo de um deploy mais antigo.Quatro corpos de erro de Flow do painel eram meio envelope, e um deles passa a chegar à /v1
A /v1/flows, a /v1/flows/{id} e a /v1/flows/{id}/json traduzem cada uma as próprias falhas de escrita e respondem { error, errorEN, code } desde a primeira linha — nenhum corpo e nenhuma string delas se mexeu aqui. A tabela que estava sem metade do envelope é o tradutor por trás da API de Flows do painel (/api/flows/*), cujos corpos eram { error, code }: só português. Quatro passam a trazer errorEN — FLOW_NOT_FOUND (404), FLOW_NOT_DRAFT (409), FLOW_REQUIRES_META_NUMBER (422), e o ramo que repassa uma recusa do Graph, a falha com maior chance de precisar de um remédio traduzido (“o token expirou”, “a janela de 24 horas fechou”) e a única das quatro cuja metade em inglês já existia e estava sendo jogada fora.Esse último ramo é o que um integrador enxerga. O registro é a única rota /v1 que encadeia esse tradutor, e a única que fala com o Graph, então uma recusa da Meta no POST /v1/flows/{id}/endpoint chega com as duas metades. Os outros três não chegam a um integrador: o registro responde os corpos de FLOW_NOT_FOUND e de FLOW_REQUIRES_META_NUMBER da própria /v1, que já eram bilíngues e não se mexeram, e nunca levanta o FLOW_NOT_DRAFT — registrar um endpoint_uri não exige rascunho.Duas mensagens em português também mudaram, e ganharam o remédio que faltava nelas. Elas são do painel; os códigos e os status HTTP estão intactos:FLOW_REQUIRES_META_NUMBER ganhou o ”— selecione um número Meta para continuar” do mesmo jeito, e esse remédio deliberadamente não é o que a /v1 dá: lá o número vem da chave de API, então a frase continua “use uma chave de um número Meta”. Nenhuma string de mensagem da /v1 se mexeu. Se você conduz a /api/flows/* direto e casa pela mensagem em vez do code, isso te quebra; o code é o contrato e não se mexeu.Uma chave de escopo tenant podia enviar de um número que o chamador nunca pediu
Corrigido. Uma chave de conta inteira (scope: "tenant") nomeia o número por requisição, com o x-whatsapp-number-id. Quando uma chave dessas mesmo assim carregava um número na própria linha — uma mitigação operacional, uma migração, um UPDATE na mão, e as três aconteceram — o worker re-resolvia o envio por esse número e descartava o header em silêncio. A mensagem saía do número de WhatsApp errado, chegava à pessoa certa, e nada em lugar nenhum reportava.Uma chave de escopo tenant passa a honrar o header que recebeu. Uma chave de escopo número continua mandando no próprio número, e isso é regra e não omissão: é a mesma que a borda já aplica, porque senão o header deixaria uma chave do número A agir sobre o número B.WebhooksAPI
Uma entrega falha agora diz por quê: o response traz a cadeia de causas
Quando uma tentativa de entrega nunca chegava ao seu endpoint, o
GET /v1/webhooks/{id}/logs guardava as mesmas três palavras para qualquer motivo:cause
do erro — e era jogado fora antes de a linha ser escrita.O response agora traz a cadeia inteira, da causa mais externa para a mais interna,
unidas por <- e marcadas com o código de erro do sistema:response continua sendo
string anulável, e uma linha FAILED com statusCode de verdade continua trazendo o corpo
do seu endpoint.O
response é string de diagnóstico, não contrato — o texto vem do runtime e pode
mudar. Decida pelo status e pelo statusCode; leia o response para saber o porquê.FlowsWebhooksChatwoot
As respostas de Flow chegam até você: flow.response_received, e um endpoint data_exchange que dá para ligar
Duas metades do mesmo buraco, fechadas juntas.As respostas ficavam guardadas e em mais lugar nenhum
Um Flow enviado chega da Meta comointeractive.nfm_reply, e a única coisa legível ali é um rótulo — Enviado / Sent. É esse rótulo que cai em content, no GET /v1/messages e no espelho do Chatwoot. As respostas eram gravadas e ficavam legíveis em GET /v1/flows/{id}/responses, mas nenhuma superfície ao vivo as entregava: o webhook parecia, para todos os efeitos, vazio.Evento novo: flow.response_received, com as respostas já parseadas.events contém só messages não o recebe, e nada reporta erro — que é exatamente a cara do relato original.response traz as respostas parseadas; o responseRaw traz os bytes crus e só vem preenchido quando o parse falhou. Uma submissão nunca é descartada por não parsear.Chatwoot. O parser de WhatsApp do próprio Chatwoot não conhece nfm_reply e renderiza uma submissão com content: null. É limitação do Chatwoot, não do número. Nas conversas espelhadas pelo Pilot Status as respostas passam a ser publicadas como nota privada — visíveis para os atendentes, invisíveis para o cliente — e a bolha pública é substituída por uma linha fixa curta no lugar do rótulo da Meta. No canal nativo de Cloud do Chatwoot nós não estamos no caminho, então lá o evento é a única forma de lê-las.data_exchange: o proxy agora é alcançável
O Pilot Status já era o endpoint criptografado que a Meta chama nos Flows data_exchange — a gente decifra, repassa JSON puro para a sua API e cifra a sua resposta na volta. Até hoje não havia como ligar isso: a URL de encaminhamento não tinha quem a escrevesse, e nenhum número tinha chave.Os dois passam a ser configuráveis — na tela de Flows e na API pública: POST /v1/numbers/{id}/flow-endpoint-key para a chave, PUT /v1/flows/{id}/endpoint para a URL, as duas atrás de flows:manage. Eles vivem em níveis diferentes, e as superfícies dizem isso: a URL é por Flow (um número serve vários formulários), a chave é por número (a Meta guarda exatamente uma chave pública por telefone).Dá para gerar uma chave ou importar a sua.- Gerar — criamos um par RSA-2048, guardamos a metade privada cifrada e registramos a pública na Meta. ⚠️ Isso substitui a chave que a Meta tem para aquele número: se você já roda um endpoint
data_exchangepróprio ali, ele para de descriptografar. Trocar por cima de uma chave viva exige confirmação explícita; a configuração inicial não pergunta, porque não há nada a destruir. - Importar — entregue a chave que você já usa. Nada muda na Meta; a única chamada ao Graph nesse caminho é uma leitura. Derivamos a metade pública do que você enviou e comparamos com a da Meta: coincidiu, é guardada e carimbada; divergiu, é recusada de saída (
FLOW_ENDPOINT_KEY_IMPORT_MISMATCH) sem gravar nada, porque guardá-la deixaria um número que reportaconfigured: truee não decifra nada; e se não conseguimos fazer a chamada, a chave fica guardada comuploadedAt: null— marcada como não verificada em vez de perdida. ⚠️ PEM protegido por passphrase é aceito, e a passphrase é usada uma vez para abri-lo e não é guardada — guarde a sua cópia.
POST de {version, action, screen, data, flow_token, flow_id, number_id} com o cabeçalho x-pilot-status-signature — HMAC-SHA256 sobre o corpo cru, o mesmo esquema dos nossos webhooks de saída. Você responde 2xx, JSON válido, contendo screen; data é opcional; não mande version (ecoamos a da Meta). O ping da Meta é respondido por nós e nunca reencaminhado.Um 200 sem screen é recusado de propósito. Repassado, a Meta renderiza como nada — o cliente fica num formulário que nunca avança e nunca dá erro. Tratamos como chamada falha: um erro repetível sobre a tela em que ele já está.Salvar a URL não registra nada na Meta — apontar o endpoint_uri da Meta para nós continua sendo um ato separado e deliberado, para que um rename não sequestre um Flow já servido pelo endpoint de outra pessoa. O recurso do endpoint reporta metaEndpointUri, drift e uma lista warnings (FLOW_ENDPOINT_NUMBER_HAS_NO_KEY é o que importa) para que uma gravação que não mudou nada na Meta não pareça uma que mudou.Guias novos: Receber as respostas de um Flow e Conecte a sua API a um Flow.APINúmerosBreaking
Mudou o padrão: número novo não importa mais o histórico do aparelho
Osettings.historyImportEnabled passa a nascer false. Número criado de hoje em diante começa a existir no instante em que conecta — os até 30 dias de conversa que o aparelho entrega na conexão são descartados em vez de guardados.Números criados antes de hoje mantêm o valor que tinham. Nada foi reescrito, e número já conectado não é afetado.Se você quer o histórico, peça na CRIAÇÃO do número:POST /v1/numbers e o POST /v1/numbers/remote-pairing passam a aceitar o bloco settings inteiro, e o 201 devolve com o que o número foi criado.Criar e depois dar PATCH não funciona — e é por isso que o bloco existe. O WhatsApp entrega o histórico numa rajada única logo após a conexão; a flag é lida mensagem a mensagem conforme elas chegam; nada consegue pedir de novo. Um PATCH depois do POST corre contra essa rajada — e no fluxo de link de pareamento não tem chance nenhuma, porque quem abre o link conecta na hora.No painel, a pergunta agora faz parte do assistente de conexão, ao lado do telefone — o único momento em que a resposta ainda muda alguma coisa.Por que o padrão virou. Guardar um mês de conversa que ninguém pediu é o padrão caro: enche o /chat de histórico que o cliente já leu no celular, e cada reconexão replica tudo de novo. Desligado é a escolha reversível para um número novo; ligado não é, depois que a rajada passou.APINúmerosWebhooks
Desligar Canais do WhatsApp num número: ignoreNewsletters
Documentado. O settings.ignoreNewsletters do PATCH /v1/numbers/{id} descarta publicação de Canal (@newsletter) na entrada: não vira conversa, não vira mensagem guardada e não dispara message.newsletter. O padrão é false, então nada muda para quem hoje recebe canal.caption na entrada de 18 de agosto: aceito pela API e invisível em todo lugar onde alguém procuraria.Parece o ignoreGroups e não é um dos campos avançados. O provedor não-oficial aplica o ignoreGroups ele mesmo, e não tem gate nenhum para @newsletter — a publicação sempre chega na plataforma, então o único lugar onde dá para recusá-la é aqui. O campo aceita só true / false, nunca o null que devolve um campo avançado ao padrão do provedor: para canal não existe padrão do provedor para onde voltar.SDK Python: o numbers.update() copia o settings chave a chave de uma allowlist que não tinha esta — o campo era descartado em silêncio, com a chamada devolvendo 200 e os canais continuando a chegar. Corrigido, sai na próxima release; curl e playground não são afetados.APIMensagensMídia
Vídeo: .mov passa a ser convertido sozinho, e o teto de 16 MB ficou explícito
Corrigido. Enviar um vídeo .mov (QuickTime) para um número da Meta Cloud API falhava. A Cloud API aceita exatamente dois tipos de vídeo no upload — video/mp4 e video/3gpp — e o .mov, que é o que o iPhone e a maioria dos Android gravam, não é um deles. O envio morria com Received file of type 'video/quicktime'.Você não precisa converter nada: .mov e .webm agora viram MP4 automaticamente antes do envio. Quando os codecs já são compatíveis (H.264 + AAC, que é o que câmera de celular grava), é só troca de container, sem recompressão — não há perda de qualidade.Vídeo tem teto de 16 MB. O limite é do WhatsApp, não nosso, e não há como contornar. Acima disso o envio falha com o novo código META_MEDIA_TOO_LARGE, que agora traz o tamanho medido em vez de repassar o erro cru do provedor. Os outros tetos, para referência: imagem 5 MB, áudio 16 MB, documento 100 MB, figurinha 100 KB estática / 500 KB animada.Enviar vídeo como mediaType: "document" não devolve mais 202. Nunca funcionou — a classe documento não tem nenhum tipo de vídeo — e é uma tentativa comum de trocar o teto de 16 MB do vídeo pelo de 100 MB do documento. Em números da Meta Cloud API a requisição passa a ser recusada na entrada:422, antes de a mensagem entrar na fila, em vez de um 202 seguido de falha opaca. Números não oficiais (Pilot Status web) não mudam — eles entregam vídeo como arquivo sem problema, e a restrição é só da Meta.Quem decide a conversão é o arquivo, não a URL. O container é identificado pelos próprios bytes, então um .mov é convertido mesmo quando a URL não tem extensão nenhuma (uma chave .bin, uma URL assinada) ou tem uma extensão que engana. Isso importa porque vídeo com rótulo errado é o pior caso possível: a Meta aceita o upload, devolve um id de mensagem, e depois simplesmente não entrega — sem sinalizar falha nenhuma. Veja o guia de mídia.APIMensagens
Enviar figurinhas: mediaType: "sticker"
Novidade. O modo de mídia direta do POST /v1/messages/send aceita mediaType: "sticker". Funciona igual em números da Meta Cloud API e em números não oficiais (Pilot Status web) — cada um tem a própria rota de figurinha por baixo, e a API escolhe por você.caption junto é recusado com 400, em vez de entregar sem o texto — nenhum provedor tem campo de legenda em figurinha, então aceitar significaria o envio “dar certo” menos aquilo que você escreveu. mediaFilename é recusado pelo mesmo motivo, e figurinha não pode ser mídia de template: cabeçalho de template é TEXT, IMAGE, VIDEO ou DOCUMENT, e não existe cabeçalho de figurinha.O arquivo precisa ser image/webp e ter exatamente 512×512 pixels — estática até 100 KB, animada até 500 KB. A dimensão não é recomendação: uma figurinha 361×363 é recusada. Essas regras são do WhatsApp, não nossas, e quem aplica é ele: arquivo com tamanho, dimensão ou formato errado ainda é aceito aqui com 202 e falha depois, no webhook message.failed, como META_MEDIA_UPLOAD_ERROR (código 131053 da Meta) com o motivo exato em error_data.details. Converta antes de enviar em vez de contar com a API para barrar. Veja o guia de mídia.Se você mandava um .webp como mediaType: "image" para aproximar uma figurinha, aquilo entregava uma imagem comum — troque para "sticker" e você recebe uma figurinha de verdade.Também nesta entrega: caption agora aparece no playground da API. O campo sempre foi aceito em envios de imagem, vídeo e documento, mas faltava no spec OpenAPI, então o playground não tinha a caixa dele.APIAnúncios
De qual anúncio veio cada conversa — e o nome da campanha
Novos:GET /v1/referrals e GET /v1/referrals/summary. Quando alguém toca num anúncio
Click-to-WhatsApp e escreve para o seu número da API oficial, o WhatsApp nos diz qual anúncio
foi clicado. Isso vinha sendo recebido e descartado; agora é guardado e pode ser lido. O
primeiro endpoint lista as mensagens que vieram de anúncio, o segundo agrupa por anúncio com
a contagem de conversas.A Meta anexa a origem apenas na primeira mensagem da conversa e nunca repete, então uma
linha equivale a uma conversa iniciada por aquele anúncio. conversations conta conversas
distintas e messages conta mensagens — duas mensagens na mesma conversa são 1 e 2, então
conversations é a contagem de leads.Os nomes. Ao lado do sourceId cru, o objeto referral passa a trazer adName,
adsetId, adsetName, campaignId, campaignName e namesResolvedAt — os mesmos rótulos
que você usa no Gerenciador de Anúncios, em vez de um opaco 120249828053880703.Nenhum endpoint resolve nome sob demanda. A resolução é assíncrona, fora do caminho da
requisição, então consultar nunca dispara chamada à Marketing API e um null pode virar nome
minutos depois sem você fazer nada. O namesResolvedAt é o que torna isso observável: null
significa “ainda não resolvido”, enquanto data preenchida com nomes nulos significa
“perguntamos e a Meta não devolveu” — anúncio apagado, ou compartilhamento ainda ausente.A atribuição começa nesta versão: não há backfill, então conversas anteriores a hoje não
carregam origem de anúncio.Docs: Atribuição de Click-to-WhatsApp.ConnectEmbed
Estilizar o botão do Connect agora renderiza o botão
Mudança de comportamento. Um link de pareamento gerado combranding.button agora renderiza só o botão — sem cabeçalho, sem card, fundo transparente. Até hoje isso exigia um segundo passo que a resposta nunca mencionava: a sua página também tinha que anexar ?mode=button na URL do iframe. Mandar branding.button e abrir a remotePairingUrl devolvida entregava a página de conexão completa, o que se lia como “o meu estilo foi descartado”.Um objeto, os dois efeitos: estilize um botão e receba um botão. Cole a remotePairingUrl devolvida no navegador e você vê exatamente o que o seu cliente vai ver.Se você quer a página cheia num link estilizado, anexe ?mode=page (ou passe mode: "page" no SDK). É o novo opt-out explícito, e ele mantém o estilo do token no botão dentro do card. Nada mais mudou: link sem estilo continua renderizando a página, e ?mode=button explícito continua renderizando o botão. Links metaFlow: "credentials" e de QR não são afetados — formulário de credenciais e linha do tempo de QR não cabem num botão, então sempre renderizam a página.@pilot-status/embed 0.2.0. O ConnectOptions.mode passa a ter padrão automático em vez de "page": omita e o SDK resolve o modo pelo token, e então dimensiona o iframe de acordo (48 px para um botão, em vez de um mínimo de 520 px). Passe "page" ou "button" explícito para decidir você mesmo.Corrigido: o bundle do SDK servido estava velho. O embed.js entregue às páginas dos clientes não era reconstruído desde 1º de julho, ou seja, era anterior ao modo botão — o PilotStatus.connect.mount(el, { mode: "button" }) renderizava a página completa, em silêncio, para todo mundo que usava o SDK. Reconstruído; o caminho de iframe cru nunca foi afetado.Corrigido: a doc mandava carregar o SDK de um host que não existe. Quatro páginas — as duas de Incorporar Conexão e as duas de Incorporar Chat — traziam <script src="https://embed.pilotstatus.com.br/embed.js">. Esse hostname não tem registro de DNS, então a tag de script não resolvia e o PilotStatus nunca era definido: nada na página funcionava, e o console do navegador culpava o DNS em vez de nós. O SDK sempre foi servido de https://pilotstatus.com.br/embed.js (que é o que a página de Sessões de Embed já usava). As seis referências agora apontam pra lá. Se você copiou o trecho antigo, troque essa URL.Docs: Embedded Signup e Modo botão.WebhooksDocs
Todos os eventos de webhook num lugar só — inclusive os da Meta
A página Eventos de webhook agora abre com um mapa de qual vocabulário cada número fala, e documenta o lado da Meta Cloud API que faltava: os 23 nomes de campo assináveis (messages, message_echoes, os cinco campos de template, phone_number_quality_update, a família de conta, calls, flows, os quatro campos de grupo, payment_configuration_update, user_preferences), o que cada um reporta e quais exigem plano pago.Também escrito por extenso pela primeira vez: número Meta entrega o envelope nativo da Meta e nunca message.received (o events: ["*"] não converte); cada mudança é entregue sozinha, então entry e entry[0].changes têm sempre exatamente um elemento; e nove campos da Meta nunca são encaminhados a webhook de cliente, nem para assinatura "*" — account_alerts, automatic_events, history, messaging_handovers, partner_solutions, security, smb_app_state_sync, standby, tracking_events.APIWebhooks
message.stories, e eventos de grupo/canal deixam de ser exclusivos de plano pago
Evento novo: message.stories. Status (stories) publicados pelos seus contatos passam a ser entregues como evento de webhook, com a mídia re-hospedada em mediaLink. Assine o evento — ou o curinga "*" — e ele começa a chegar.É dirigido por demanda, como grupo: o provedor não envia Status enquanto ninguém pedir, e volta a não enviar quando a última assinatura sai. Isso é proposital. Antes desta mudança um número conectado recebia o Status de todo contato que enxergava e a plataforma descartava todos no fim do pipeline — medido em 1.869 eventos por dia, 17% de tudo que entrava na ingestão, sem nenhum consumidor.Status não é persistido: não cria conversa nem mensagem, e não aparece em GET /v1/messages. O WhatsApp expira stories em 24h; o mediaLink que re-hospedamos é durável, o story não. Também não há campo to — um Status é difundido para a lista de contatos de quem publica, não endereçado ao seu número.message.group, message.newsletter e message.stories deixam de ser exclusivos de plano pago. Eles ficavam escondidos do seletor de eventos em planos não pagos; essa restrição era legado e foi removida. Os três são selecionáveis em qualquer plano.APIWebhooksDashboard
Configuração do número, e histórico não inunda mais o seu webhook
Mudança de comportamento. Quando um número conecta, o WhatsApp entrega ao provedor o histórico do aparelho. Até agora essas mensagens antigas eram encaminhadas ao seu webhook comomessage.received / message.group / message.newsletter — ou seja, cada reconexão re-entregava até 30 dias de conversa, sem nada no payload que permitisse distinguir isso do tráfego que tinha acabado de chegar. Elas continuam sendo importadas para o chat; deixaram de ser entregues. Se quiser o comportamento antigo num número, ligue settings.webhookHistoricalMessages.createdAt passou a significar o que diz. Nos webhooks de mensagem ele carrega quando a mensagem realmente aconteceu, segundo o provedor, em vez do momento em que a processamos. Mesmo campo, mesmo tipo, mesmo formato ISO 8601.Novo: PATCH /v1/numbers/{id}. Configuração por número, parcial — política de retenção mais um bloco settings com o comportamento do histórico e, para números não-oficiais (conectados por QR), os advanced settings do provedor: rejectCall, msgRejectCall, alwaysOnline, readMessages, ignoreGroups, ignoreStatus. Veja a referência. Se você chamava POST /v1/numbers/{id}/settings, essa rota nunca existiu e vinha respondendo 404 em silêncio.Caminho /v1 desconhecido agora devolve JSON. Um caminho errado renderizava a página HTML de 404, o que quebrava clientes HTTP tipados sem dizer o motivo. Agora responde { "error": "Not found", "code": "ROUTE_NOT_FOUND" } como todo o resto da API.Disponível nos SDKs Node (0.5.0), Python (1.4.0) e n8n (1.3.0), e no servidor MCP.API
Endpoint de logout de instância WhatsApp
Novo endpointPOST /v1/numbers/{id}/logout para desconectar uma instância WhatsApp sem removê-la do banco de dados. Isso é útil quando você precisa desconectar um número temporariamente para solução de problemas ou reconexão a um dispositivo diferente, sem perder a configuração da instância. Veja a referência de POST /v1/numbers/{id}/logout.API
Nota de voz com waveform (voice mediaType)
Um novo "voice" mediaType permite controlar como a mensagem de áudio é renderizada. "audio" envia uma nota de voz (PTT) no player padrão; "voice" envia com a forma de onda (ondinha) visível. Em números Meta Cloud API a waveform é ativada quando o servidor envia voice: true no payload de áudio. Ambos os tipos são normalizados no servidor para OGG/Opus mono (metadados removidos, start_time zero), garantindo que todo áudio seja compatível mesmo quando o upload original era um WebM do navegador ou uma gravação do iOS. Veja a referência de POST /v1/messages/send.APIDashboard
Carrossel: preview no editor, variáveis e o OpenAPI
Montar um carrossel no editor de templates ficou fiel de ponta a ponta. O preview ao vivo volta a renderizar a tira de cards (antes não mostrava nada), o editor aplica a exclusividade do carrossel — um carrossel não leva cabeçalho, botões de template, botão de lista nem card de pedido, então esses controles somem ou desabilitam enquanto há um carrossel — e{{variáveis}} dentro de um card (o corpo ou um botão) passam a ser reconhecidas e substituídas no preview, em vez de ficarem como token cru.No lado da API, o campo carousel entrou no OpenAPI de POST /v1/messages/send, então ele aparece no playground interativo e nos clientes gerados. Também corrigimos a doc: um botão de URL no carrossel de sessão só é impossível em números Meta — em números não oficiais (Pilot Status web) o botão de URL sai no avulso.APIDashboard
Mensagens em carrossel
Envie um carrossel — um conjunto de cards de mídia rolável na horizontal — numa única mensagem. Um novo campocarousel em POST /v1/messages/send carrega de 2 a 10 cards, cada um com sua própria imagem, uma linha curta de texto e até dois botões, e o editor de templates ganha um construtor de carrossel com preview ao vivo e arrastável, para você reordenar os cards enquanto monta. Um carrossel de sessão é livre e não precisa de aprovação, mas seus botões são apenas de resposta rápida e ele só é entregue dentro da janela de atendimento de 24 horas — a mesma requisição funciona em números oficiais e não oficiais. Um carrossel de template é um template aprovado pela Meta, então é entregue a qualquer momento e um card pode combinar um botão de resposta rápida com um botão de URL.API
A exclusão de template diz a verdade
DELETE /v1/templates/{id} não reporta mais sucesso quando a Meta se recusa a remover um template. Quando a Meta rejeita a exclusão — por exemplo, quando o token de acesso não tem permissão na conta do WhatsApp Business — o endpoint agora responde 409 com uma mensagem bilíngue nomeando o template e o motivo, e o template é mantido localmente, porque ainda existe na Meta. Só uma remoção genuína dos dois lados retorna { "deleted": true }.DashboardChat
Botões, carrosséis e cards de pedido entregues aparecem no chat
Mensagens que chegam com botões, um carrossel ou um card de pedido agora aparecem como seu balão de verdade na conversa e nos logs de mensagens, em vez de virarem texto puro. Respostas de template mostram seus botões, carrosséis rolam pelos seus cards e cards de pedido listam seus itens — então a conversa é lida do jeito que o destinatário realmente viu.Dashboard
Número Meta com acesso revogado é detectado
Quando um número conectado pela API oficial da Meta perde o acesso — o app foi removido da conta do WhatsApp Business, ou a própria WABA foi excluída — o painel agora detecta e marca o número com o selo “Conexão perdida”. Antes o número simplesmente ficava mudo, sem nenhum sinal de que a Meta havia cortado o acesso; agora o selo avisa para você reconectar.Correção
Links de botão de URL e contagem de variáveis de template
Os exemplos de botão de URL não duplicam mais a URL base quando o valor de exemplo já a inclui, então o link do preview e do envio é o real. E o envio de template agora passa as variáveis do próprio corpo, então um template que usa uma{{variável}} tanto no corpo quanto num botão não falha mais com erro de contagem de parâmetros.Conexão
Embed só do botão para o Embedded Signup
O Embedded Signup hospedado ganha um modo de embed só do botão: coloque apenas o botão de conectar do Facebook na sua própria página, em vez do fluxo inteiro. O estilo do botão viaja no token do embed, e uma conexão concluída retorna já pareada com o número conectado.API
A chave de API de tenant agora age em qualquer número
A chave com escopo de tenant gerenciava números mas não podia usá-los — enviar exigia uma chave separada por número. Agora ela chama qualquer endpoint por número, de qualquer número do tenant, inclusive envio, indicando o número no cabeçalhox-whatsapp-number-id (aceita o id devolvido por GET /v1/numbers). Sem o cabeçalho, os endpoints por número continuam respondendo 403 TENANT_SCOPE_NOT_ALLOWED; um id de outro tenant responde 404. Veja Autenticação.DashboardWebhooks
Entregas de webhook pelo número ativo
A tela de Webhooks lista as entregas do número selecionado. As entregas gravadas sem número — eventos de status de mensagem — apareciam em todos os números ao mesmo tempo; agora são atribuídas ao número que as gerou.DashboardTemplates
Listas e pagamentos como botões de template
O Botão de Lista no editor de template monta uma lista interativa — texto do botão, seções e opções — com contadores ao vivo e um preview que abre as opções numa gaveta inferior, como o próprio WhatsApp faz. Um template com lista é enviado como mensagem interativa, e não como template aprovado: em número oficial ele não vai para aprovação da Meta e só é entregue dentro da janela de 24 horas — o editor avisa isso enquanto você monta.O Botão de Pagamento reúne as duas formas de cobrança num lugar só: o botão de copiar código PIX e o card de pedido com itens, que antes ficava numa seção separada abaixo do editor. Templates salvos antes continuam funcionando igual.Dashboard
Detecção de número que parou de receber
Uma sessão do WhatsApp pode travar só de um lado: o número continua enviando, a conexão se diz saudável e nenhuma mensagem entra por horas. O painel passa a acompanhar o ritmo de recebimento de cada número e sinaliza quando as mensagens param de chegar enquanto o envio continua funcionando — o selo deixa de afirmar uma saúde que não foi verificada.Dashboard
Shadowban web sinalizado já na primeira recusa
Quando o WhatsApp recusa envios por uma conexão não-oficial, o número passa a ser sinalizado na hora, em vez de só após três recusas na mesma janela — quem envia pela API poucas vezes por semana podia ficar semanas bloqueado com o selo verde. A sinalização se resolve sozinha assim que um envio volta a funcionar.Dashboard
Monte listas direto do chat
O composer do chat ganha a ação Lista: monte o texto do botão, as seções e as linhas com validação ao vivo e preview em tempo real, e envie — tanto em números oficiais quanto não oficiais. Listas enviadas agora aparecem como um balão de lista de verdade na conversa e nos logs de mensagens, e responder a uma mensagem ao enviar a lista mantém a citação.API
Mensagens de lista
Novo campolist em POST /v1/messages/send — envie uma lista interativa de seleção única: text vira o corpo da mensagem e buttonText abre um menu de até 10 linhas agrupadas em até 10 seções. Funciona em números oficiais e não oficiais com a mesma requisição, e a seleção do destinatário chega como uma resposta de entrada normal.DashboardTemplates
Insights de template
Todo template já enviado à Meta ganha um botão de insights na lista de templates: enviadas, entregues e lidas com taxa de leitura, cliques por botão, selos de qualidade e status, e seletor de período de 7/30/90 dias — usando o template analytics da Meta (habilitado automaticamente na sua conta na primeira abertura).Dashboard
Preview do perfil comercial nos números oficiais
O bloco de um número conectado pela API oficial da Meta agora mostra o preview do perfil comercial do WhatsApp exatamente como os clientes veem — foto, nome, telefone, descrição, categoria e site — com um botão Compartilhar que copia o linkwa.me do número.APITemplatesPagamentos
API de janela de atendimento, preview por tipo de conexão e pagamentos PIX
Novo endpointGET /v1/service-window — verifique se a janela de atendimento de 24 horas está aberta ou fechada para um contato antes de enviar: passe destinationNumber e receba { open, windowType, lastInboundAt, expiresAt }. Números Meta reportam a janela real de 24h; números não oficiais sempre reportam aberta.Preview do editor de templates agora mostra exatamente o que o destinatário vê para o número conectado: o card de template da Meta em números oficiais, e a forma entregue (texto + botões) em números não oficiais — com substituição ao vivo das {{variáveis}} usando seus valores de exemplo, e o bloco de preview acompanhando o scroll da página.Botão de pagamento PIX (PAYMENT_REQUEST) agora funciona em todos os tipos de número: números oficiais com WhatsApp Pay renderizam o card de pagamento nativo, e números não oficiais entregam um botão “Copiar código Pix” com o BR Code completo (sem limite de tamanho).Card de pedido itemizado: templates podem carregar um bloco de pedido (itens, quantidades, total e forma de pagamento). Números não oficiais EVO_V2 entregam como um card de pedido de verdade — acompanhado do texto e botões do template numa primeira mensagem quando presentes. Campos numéricos (preço, quantidade, totais) aceitam {{variável}} resolvida no envio.Também: o modal “enviar pela API” agora mostra o nome do template como templateId, e a submissão de templates ORDER_DETAILS à Meta passa a ser tentada para contas com pagamentos habilitados em vez de recusada localmente.API
API de preços de mensagem
Novo endpointGET /v1/meta/pricing — o preço por mensagem publicado do Meta / WhatsApp para um mercado + moeda + categoria (ex.: ?market=BR¤cy=BRL&category=marketing → { "pricePerMessage": 0.3217 }). Chame sem parâmetros para listar mercados, moedas e categorias, e adicione ?tiers=1 para os volume tiers. Também disponível como ferramenta MCP meta_pricing_get e no playground interativo.API
Liste seus logs de mensagem pela API
Novo endpointGET /v1/messages — pagine as mensagens enviadas e recebidas pelo seu número sem abrir o painel. Filtre por direção, status, período e número de telefone; cada linha traz o mesmo messageId que você já usa em GET /v1/messages/{id}, então uma chamada de lista e uma de status conversam entre si. Também disponível como ferramenta MCP messages_list.Dashboard