Pular para o conteúdo
Páginas da documentação

Mensagens

Identidade do contato, envio idempotente, estados do envio, leitura e janela de atendimento.

Abrir o Markdown

Identidade do contato

A Meta identifica cada pessoa pelo BSUID (business-scoped user id), que o hub expõe como userId. O BSUID vale dentro do portfólio de negócios do seu cliente final (o mesmo usuário tem outro BSUID em outro portfólio). Formato: código do país, ponto e até 128 alfanuméricos, por exemplo BR.13491208655302741918.

  • userId é a chave primária do contato. Guarde e procure pelo userId; o telefone (waId, só dígitos) pode faltar, porque a Meta omite o telefone de quem usa nome de usuário.
  • parentUserId só vem quando a conta do cliente tem parent BSUID; username só quando o contato tem nome de usuário.
  • Identidade nos eventos: { userId, parentUserId, waId, username, name }, com null no que a Meta não mandou.
  • Troca de identidade chega como whatsapp.identity.updated (o contato trocou de número ou de BSUID). Use data.previous.userId para achar o contato e grave data.current. O hub já move a janela de atendimento para a identidade nova.

Todas as rotas desta seção estão sob /v1/connections/:connectionId. Conexão de outro app responde 404 CONNECTION_NOT_FOUND; enviar exige conexão active (senão 409 CONNECTION_NOT_ACTIVE).

As recusas destas rotas (mensagens, mídia, templates) seguem o formato { statusCode, error, code, message, ...detalhes }. Decida pelo code.

Enviar

POST /v1/connections/:connectionId/messages com o header Idempotency-Key (1 a 128 caracteres entre A-Z a-z 0-9 . _ : -, obrigatório; sem ele, 400 IDEMPOTENCY_KEY_REQUIRED). Responde 202 { "message": ... }: o envio entra numa fila e o resultado chega por evento.

Corpo estrito (campo extra é rejeitado), um formato por type:

type Campo Conteúdo
text text { body, previewUrl? }, body até 4096
image, video image / video { mediaId, caption? } (legenda até 1024)
document document { mediaId, caption?, filename? }
audio audio { mediaId, voice? }; voice: true (mensagem de voz) só com audio/ogg
sticker sticker { mediaId }
location location { latitude, longitude, name?, address? }
contacts contacts lista de 1 a 257 contatos no formato da Meta em camelCase (name.formattedName, phones[], emails[], urls[], addresses[], org, birthday)
interactive interactive objeto plano com kind, header?, body, footer? (até 60) e os campos do kind, sem o aninhamento da Meta (action, parameters): kind: "buttons" com buttons: [{ id, title }] (1 a 3, título até 20 e único; body até 1024; header de texto { type: "text", text } ou de mídia do hub { type: "image" | "video" | "document", mediaId, filename? }), kind: "list" com button (texto do botão, até 20) e sections: [{ title?, rows: [{ id, title, description? }] }] (até 10 seções e 10 linhas no total; body até 4096; só header de texto) ou kind: "cta_url" com displayText (até 20) e url https (body até 1024; só header de texto)
reaction reaction { wamid, emoji }; emoji: "" remove a reação. Reação não aceita replyTo
template template { templateId, parameters? } (id do hub) ou { name, language, parameters? } (ver Parâmetros de template)

Todo tipo leva to: { userId?, phone? } (pelo menos um) e, fora reaction, replyTo? com o wamid da mensagem citada. Com os dois, o telefone vale. mediaId é sempre o id de uma mídia que você subiu pelo hub (Mídia), da mesma conexão.

{
  "to": { "userId": "BR.13491208655302741918" },
  "type": "text",
  "text": { "body": "Seu pedido saiu para entrega." }
}

Idempotência. A Idempotency-Key é única por app. Repetir o pedido com a mesma chave e o mesmo corpo devolve 200 com o estado atual da mensagem, sem novo envio, mesmo que a conexão tenha caído depois. A mesma chave com corpo diferente responde 409 IDEMPOTENCY_KEY_REUSED. O telefone é normalizado antes da comparação (+5543999990000 e 5543999990000 são o mesmo pedido). A chave vale enquanto a mensagem existe no hub, ou seja, 30 dias; depois disso a mesma chave cria um envio novo.

Rota de marketing. Template MARKETING de uma conta que aceitou os termos do Marketing Messages API (connection.updated com meta.accountEvent: "MM_LITE_TERMS_SIGNED") sai pela rota marketing_messages da Meta; o resto sai por messages. A rota usada aparece em message.route. Sem os termos, o marketing sai pelo Cloud API comum até a Meta encerrar esse caminho (contas criadas a partir de 13/04/2027 não podem; todas param em 13/10/2027).

Recusas na hora do pedido (nada é enviado):

Status code Quando
400 IDEMPOTENCY_KEY_REQUIRED header ausente ou fora do formato
409 IDEMPOTENCY_KEY_REUSED chave já usada com outro corpo
404 CONNECTION_NOT_FOUND conexão inexistente ou de outro app
409 CONNECTION_NOT_ACTIVE conexão não está active ou está sem credencial
404 MEDIA_NOT_FOUND mídia inexistente, de outra conexão ou ainda não pronta
410 MEDIA_EXPIRED a mídia venceu na Meta (29 dias depois do upload): suba de novo
400 MEDIA_KIND_MISMATCH a mídia não é da família que o tipo exige (por exemplo um PDF em image, ou voice fora de audio/ogg), inclusive no header de interativo e de template
404 TEMPLATE_NOT_FOUND template fora do cache da conta da conexão
422 TEMPLATE_NOT_APPROVED template não está APPROVED
400 TEMPLATE_PARAMETERS_MISMATCH parâmetros não batem com o template (traz component, nunca os valores)
400 TEMPLATE_COMPONENT_UNSUPPORTED o template tem um componente que o hub não preenche (traz component)
400 RECIPIENT_PHONE_REQUIRED template AUTHENTICATION sem to.phone (a Meta não aceita BSUID nele)
422 OUTSIDE_SERVICE_WINDOW mensagem livre (não template) para um contato com a janela de atendimento vencida
400 (validação) corpo fora do schema

Estados do envio

message (no 202, no 200 idempotente e nas leituras):

{
  "id": "...",
  "connectionId": "...",
  "type": "text",
  "state": "accepted",
  "route": "messages",
  "wamid": "wamid.HBgM...",
  "pacing": null,
  "errorCode": null,
  "metaErrorCode": null,
  "deliveryState": "delivered",
  "deliveryStateAt": "2026-10-23T15:00:05.000Z",
  "createdAt": "2026-10-23T15:00:00.000Z",
  "updatedAt": "2026-10-23T15:00:05.000Z"
}

Nenhuma resposta nem evento traz o conteúdo ou o destinatário.

state Significado O que fazer
pending Na fila do hub (inclusive esperando a vazão do número ou um retry por limite da Meta) Esperar
sending O hub começou a submissão à Meta Esperar
accepted A Meta aceitou (wamid preenchido). A entrega segue em deliveryState Acompanhar whatsapp.message.status
failed Recusado, pelo hub ou pela Meta, com errorCode (e metaErrorCode quando veio da Meta) Corrigir a causa antes de enviar de novo
uncertain O hub não sabe se a Meta recebeu (timeout, queda de rede, 5xx, 131000/131016/133004) Não reenvie às cegas. Espere: se a Meta mandar um status com a referência do envio, a mensagem vira accepted sozinha. Se nada chegar, a equipe Matra encerra como abandoned
abandoned Entrega desconhecida, encerrada pela equipe Matra Reenviar é decisão humana (pode duplicar). Não é recusa da Meta

pacing vem no accepted quando a Meta informa o ritmo: accepted, held_for_quality_assessment (a Meta segura a mensagem para avaliar qualidade) ou paused (entrega pausada).

deliveryState é o melhor status de entrega visto: sent < delivered < read < played. failed só aparece se nada melhor foi visto, e delivered/read/played substituem um failed anterior (entrega provada vale mais). failed em deliveryState não muda o state (a Meta aceitou a submissão e falhou depois).

uncertain e a conciliação. O hub manda o id da mensagem para a Meta em biz_opaque_callback_data, e o status da Meta o devolve. Quando o status chega para um envio uncertain (ou abandoned), a mensagem vira accepted e sai um whatsapp.message.status source: "hub" com meta: { "reconciled": true }. Se o envio já estava abandoned, o evento traz também "overridesAdminResolution": true: a evidência da Meta vale sobre a decisão da equipe Matra, e quem já reenviou pode ter uma duplicata. Um accepted com overridesAdminResolution: true vale sobre qualquer abandoned do mesmo messageId, qualquer que seja o createdAt dos dois: se a conciliação chega no instante em que o admin encerra o envio, o abandoned pode sair depois dele. Em testes com o app real, o campo voltou nos status de texto, interativo, reação, imagem, documento e template.

Status de um envio feito por outro workspace (o dono anterior de um número que mudou de workspace) não chega ao seu: o hub reconhece o biz_opaque_callback_data de outra conexão e, sem correspondência pelo wamid na sua, descarta o item (nenhum whatsapp.message.status, e a referência do outro envio não é guardada). O evento source: "hub" de uma transição sai logo depois dela; se essa emissão falhar, uma varredura emite o evento em até 1 h, com timestamp igual à hora da última alteração gravada do envio (em geral a da transição; um status da Meta que chegou no meio a adianta).

errorCode de envio recusado (trate valores desconhecidos como erro genérico):

errorCode Causa
outside_service_window Meta 131047: fora da janela de 24 h, só template
user_opted_out Meta 131050: o contato não quer receber marketing; não repita
bsuid_not_supported Meta 131062: o tipo não aceita BSUID; envie por telefone
marketing_disabled_on_cloud_api Meta 131063
template_not_found_at_meta Meta 132001; o hub ressincroniza os templates da conta
template_paused Meta 132015
credential_invalid Meta 190/0: o cliente precisa reconectar
permission_denied Meta 10/200/131005
payment_method_required Meta 131042: problema no método de pagamento da conta. Passado o nível gratuito, sem método válido a Meta não entrega mensagens de serviço; o cliente acerta o pagamento no Gerenciador do WhatsApp antes de enviar de novo
rate_limited 8 recusas seguidas por limite da Meta
connection_inactive, credential_unavailable, template_not_approved, media_expired A situação mudou entre o pedido e o envio
meta:<código> Outra recusa da Meta, com o código dela (meta:unknown sem código)
resolved_by_admin Em abandoned

Ler envios

Método e rota O que faz Respostas
GET …/messages Lista (paginada); filtros idempotencyKey e state 200, 404
GET …/messages/:messageId Busca um envio 200, 404 MESSAGE_NOT_FOUND

Parâmetros de template

parameters tem header?, body? e buttons?:

  • header: { type: "text", value }, { type: "image" | "video" | "document", mediaId, filename? } (mídia do hub) ou { type: "location", latitude, longitude, name?, address? }. Tem de ser do formato do header do template.
  • body: lista de { name?, value }. Template posicional: os n valores em ordem (ou todos com name de 1 a n). Template nomeado: todo valor com name, o mesmo conjunto do template, em qualquer ordem.
  • buttons: lista de { index, type, value } com type quick_reply (payload), url (sufixo da URL dinâmica; o hub faz o encode) ou copy_code (cupom até 20). Em template AUTHENTICATION, o código vai no botão de índice dele com type: "url".

O hub recusa com TEMPLATE_COMPONENT_UNSUPPORTED o que não preenche: carrossel, oferta por tempo limitado, botões de Flow, catálogo e MPM, botão OTP fora do preset copy code, URL com variável fora do fim, e parâmetro currency ou date_time. A detecção de currency/date_time é pelo template em cache; se escapar, a Meta recusa no envio (failed com meta:<código>).

Confirmação de leitura

POST …/read-receipts com { wamid, typingIndicator? }: marca a mensagem recebida (e as anteriores) como lida; com typingIndicator: true mostra também "digitando". Síncrono, no limite de envio.

Status code Quando
200 { "success": true }
400 INVALID_MESSAGE_ID a Meta não reconhece o wamid (131009)
422 META_READ_REJECTED outra recusa da Meta, com metaErrorCode
502 META_UNAVAILABLE resposta incerta; repetir é seguro

Janela de atendimento

GET …/windows?userId=<BSUID> ou ?phone=<dígitos> → { "window": { "open": true, "expiresAt": "...", "freeEntryPoint": null } }, ou { "window": null } sem registro. freeEntryPoint é { "expiresAt": "..." } enquanto vale uma janela de ponto de entrada gratuito (ver Custos); ela só muda o preço, não libera mensagem livre fora das 24 h. A janela abre ou estende por 24 h a cada mensagem recebida do contato (data.window do whatsapp.message.received traz o expiresAt); eco, histórico, mensagem de sistema e de grupo não contam.

Lacuna conhecida: a Meta também reabre a janela quando o contato liga para o número, e o hub não vê ligações. Para quem usa Calling, um 422 OUTSIDE_SERVICE_WINDOW pode recusar um envio que a Meta aceitaria. Sem registro de janela o hub não bloqueia: o envio segue e a Meta decide.