O que são BSUIDs
+Business-Scoped User ID — identificador único para cada par (usuário + portfólio de negócio). Gerado pela Meta, sempre presente nos webhooks a partir de 31 de março.
+ +| Propriedade | Detalhe |
|---|---|
| Formato | {ISO 3166-1 alpha-2}.{alfanuméricos} — ex: BR.99887766554433221100 |
| Tamanho | Código do país + . + até 128 caracteres alfanuméricos |
| Escopo | Único por par portfólio de negócio + usuário (não global) |
| Persistência | Regenerado quando o usuário troca de número de telefone |
| Parent BSUID | Formato {CC}.ENT.{alfanuméricos} — para portfólios vinculados (linked portfolios) |
Atenção: BSUIDs não são globais. Se você tem múltiplos portfólios, o mesmo usuário terá BSUIDs diferentes em cada um. E se o usuário trocar de número, o BSUID muda — a Meta envia um webhook user_id_update com o antigo e o novo.
Formato: Trate o BSUID como string opaca. Armazene e envie exatamente como recebido — não trunque, não remova o código do país, não altere casing. O tamanho pode variar (até 128 chars após o ponto), então use TEXT no banco, não VARCHAR(n).
Por que BSUIDs existem
+O WhatsApp vai lançar usernames mais adiante em 2026. Quando um usuário adotar um username, poderá esconder o telefone de empresas nos webhooks.
+Sem BSUIDs, uma empresa que recebe mensagem de um usuário com username não teria como identificá-lo — perderia o contexto, criaria duplicatas e quebraria fluxos.
+BSUIDs chegam antes dos usernames. A Meta já envia BSUIDs em todos os webhooks de produção desde 31/março, mesmo antes de usernames estarem disponíveis para usuários. Isso permite que integrações se adaptem antecipadamente.
+ +O que muda nos webhooks
+ +Campos novos (sempre presentes a partir de 31/março)
+Mensagens recebidas
+-
+
contacts[].user_id— BSUID
+ contacts[].profile.username— se adotado
+ messages[].from_user_id— BSUID remetente
+
Status (sent/delivered/read)
+-
+
contacts[]— novo em status webhooks
+ contacts[].user_id— BSUID
+ statuses[].recipient_user_id— BSUID destino
+
Obs: contacts[] é omitido em status failed. username é omitido em status sent.
Campos que podem ser omitidos
+Podem desaparecer
+-
+
contacts[].wa_id— telefone
+ messages[].from— telefone remetente
+ statuses[].recipient_id— telefone destino
+
Quando são omitidos?
+-
+
- Usuário adotou username +
- E não houve interação nos últimos 30 dias por número de negócio +
- E usuário não está no Contact Book +
Regra dos 30 dias: A visibilidade do telefone é avaliada por número de negócio. Se você mandou mensagem do número A há 15 dias, o telefone aparece nos webhooks do número A — mas NÃO nos do número B do mesmo portfólio, a menos que B também tenha interagido.
+Comparação de payload: antes vs depois
+ +{
+ "contacts": [{
+ "wa_id": "5511999887766",
+ "profile": { "name": "João" }
+ }],
+ "messages": [{
+ "from": "5511999887766",
+ "type": "text",
+ "text": { "body": "Olá" }
+ // Sem user_id, from_user_id ou username
+ }]
+}
+ {
+ "contacts": [{
+ "user_id": "BR.99887766554433221100", // ← NOVO: BSUID
+ "wa_id": "5511999887766", // presente (interação < 30d)
+ "profile": {
+ "name": "João",
+ "username": "joao.silva" // ← NOVO: se adotou username
+ }
+ }],
+ "messages": [{
+ "from": "5511999887766", // presente
+ "from_user_id": "BR.99887766554433221100", // ← NOVO: BSUID
+ "type": "text",
+ "text": { "body": "Olá" }
+ }]
+}
+ {
+ "contacts": [{
+ "user_id": "BR.99887766554433221100", // ← BSUID: sempre presente
+ // wa_id: OMITIDO (username + sem interação 30d)
+ "profile": {
+ "name": "João",
+ "username": "joao.silva"
+ }
+ }],
+ "messages": [{
+ // from: OMITIDO
+ "from_user_id": "BR.99887766554433221100", // ← único identificador
+ "type": "text",
+ "text": { "body": "Olá" }
+ }]
+}
+ Send API: to vs recipient
+
+ A Meta adiciona o campo recipient para enviar mensagens por BSUID. O campo to (telefone) continua funcionando.
| Cenário | Campo | Disponível |
|---|---|---|
| Tem telefone | "to": "5511999..." | Sempre |
| Só tem BSUID | "recipient": "BR.998..." | Maio/2026 (data exata TBD) |
| Tem ambos | "to" | Phone tem precedência |
| BSUID antes de maio | — | Error 131062 |
Error 131062: BSUID recipients not supported for this message type. Retornado se você tentar enviar por BSUID antes de maio/2026. Classifique como erro de usuário — não faça retry.
Auth templates: Templates de autenticação (one-tap, zero-tap, copy code) não suportam envio por BSUID — exigem número de telefone. Isso vale mesmo após maio/2026.
+Resposta da Send API
+Quando você envia para telefone, a resposta inclui wa_id (telefone) mas não inclui user_id. Quando envia para BSUID, inclui user_id mas não inclui wa_id (a menos que o telefone esteja disponível via Contact Book).
function buildSendPayload(phone, bsuid) {
+ if (phone) {
+ return { to: phone }; // Sempre funciona
+ } else if (bsuid && isAfterMay2026()) {
+ return { recipient: bsuid }; // Só a partir de maio/2026
+ } else {
+ logWarning("Sem identificador válido para envio");
+ return null;
+ }
+}
+ Webhook user_id_update
+
+ Quando um usuário troca de número de telefone, o BSUID é regenerado. A Meta envia um webhook dedicado com o BSUID anterior e o atual.
+ +{
+ "field": "user_id_update",
+ "value": {
+ "contacts": [{
+ "profile": { "name": "João" },
+ "wa_id": "5511999887766", // se disponível
+ "user_id": "BR.NOVO_BSUID..." // BSUID atual
+ }],
+ "user_id_update": [{
+ "wa_id": "5511999887766",
+ "detail": "User id for João has been updated.",
+ "user_id": {
+ "previous": "BR.ANTIGO_BSUID...",
+ "current": "BR.NOVO_BSUID..."
+ },
+ "timestamp": "1711756800"
+ }]
+ }
+}
+ Crítico: Se você não tratar user_id_update, contatos que trocam de número "desaparecem" — o BSUID antigo não encontra mais ninguém. Inscreva seu app no campo user_id_update no App Dashboard.
Além do webhook dedicado, a Meta também envia uma system message de tipo user_changed_user_id com o texto User <NAME> changed from <OLD_BSUID> to <NEW_BSUID>.
Contact Book (início de abril/2026)
+ +| Propriedade | Detalhe |
|---|---|
| Hospedagem | Meta-hosted — sem integração necessária |
| Gatilho | Enviar ou receber mensagem/ligação após o lançamento |
| Retroatividade | NÃO retroativo — só captura interações pós-lançamento |
| Escopo | Nível de portfólio de negócio (não individual por número) |
| Desabilitar | Disponível desde 16/março/2026 em Meta Business Suite > Business settings > Business info |
| Ao desabilitar | Para de armazenar e deleta dados existentes. Reabilitar começa do zero. |
| Portfólios vinculados | Dados não são sincronizados entre portfólios — cada um é independente |
| Local storage | Modo opcional em que o Contact Book se comporta diferente com REQUEST_CONTACT_INFO (ver callout abaixo) |
REQUEST_CONTACT_INFO e Contact Book: Por padrão, quando o usuário compartilha o telefone via este botão, o telefone é adicionado automaticamente ao Contact Book. Exceção: se você habilitou local storage no Contact Book, o telefone não é adicionado automaticamente — nesse caso, envie uma mensagem para o telefone do usuário para capturá-lo.
+REQUEST_CONTACT_INFO (início de maio/2026)
+Botão para templates de utility e marketing que solicita o telefone do usuário. Quando o usuário toca, a Meta dispara um webhook de contatos com telefone e vCard.
+ +{
+ "type": "buttons",
+ "buttons": [{ "type": "REQUEST_CONTACT_INFO" }]
+}
+// Botão não é customizável e não requer parâmetros no envio
+ Parent BSUIDs (portfólios vinculados)
+Se a sua empresa tem portfólios vinculados (linked portfolios), a Meta pode fornecer um parent BSUID que funciona em todos os portfólios vinculados.
+ +| Propriedade | Detalhe |
|---|---|
| Formato | {CC}.ENT.{alfanuméricos} — ex: US.ENT.11815799212886844830 |
| Webhook | contacts[].parent_user_id, messages[].from_parent_user_id |
| Uso | Qualquer número de negócio nos portfólios vinculados pode usar o parent BSUID |
| Ativação | Requer contato com o representante Meta (POC) |
Para tech providers e solution partners
+ +Regra crítica para providers: O BSUID do cliente deve ser usado com os números de telefone do portfólio do cliente. Usar o BSUID do cliente com números do seu portfólio vai falhar.
+Se você é tech provider ou solution partner, garanta que:
+-
+
- Cada cliente tem seu próprio conjunto de BSUIDs (escopo por portfólio) +
- Seu sistema associa BSUIDs ao portfólio correto +
- O envio por BSUID usa sempre o número de telefone do portfólio do cliente +
O que pode quebrar
+ +| Risco | Severidade | Causa |
|---|---|---|
| Contatos perdidos | Alto | Sistema cria novo contato em vez de associar ao existente (sem lookup por BSUID) |
| Contatos duplicados | Alto | Mesmo usuário com dois registros: um pelo phone, outro pelo BSUID |
| Envio falha | Alto | Contato BSUID-only + envio por phone = mensagem não vai |
| UI crashes | Médio | phone.replace() ou wa.me/phone quando phone é null |
| Histórico perdido | Médio | user_id_update não tratado → contato "desaparece" ao trocar número |
| Campanhas falham | Médio | Automações que dependem de phone como destinatário |
Migração: roteiro completo
+ +1. Auditar código
+Busque todas as referências que assumem que from/wa_id são sempre números E.164. Inclua lookups por telefone, validações, links wa.me/, CRM e relatórios.
2. Adaptar banco de dados
+ +-- 1. Colunas BSUID e username
+ALTER TABLE contacts ADD COLUMN IF NOT EXISTS bsuid TEXT;
+ALTER TABLE contacts ADD COLUMN IF NOT EXISTS username TEXT;
+ALTER TABLE chats ADD COLUMN IF NOT EXISTS bsuid TEXT;
+
+-- 2. phone_id aceita NULL (CRÍTICO)
+ALTER TABLE contacts ALTER COLUMN phone_id DROP NOT NULL;
+
+-- 3. Tabela de mapeamento (auditoria)
+CREATE TABLE IF NOT EXISTS bsuid_phone_mappings (
+ id BIGSERIAL PRIMARY KEY,
+ bsuid TEXT NOT NULL,
+ phone_id TEXT,
+ instance_id UUID NOT NULL,
+ first_seen TIMESTAMPTZ DEFAULT NOW(),
+ last_seen TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- 4. Indexes únicos
+CREATE UNIQUE INDEX IF NOT EXISTS idx_contacts_bsuid_instance
+ ON contacts (bsuid, instance_id) WHERE bsuid IS NOT NULL;
+CREATE UNIQUE INDEX IF NOT EXISTS idx_chats_bsuid_instance
+ ON chats (bsuid, instance_id) WHERE bsuid IS NOT NULL;
+ 3. Parsear novos campos do webhook
+ +// Webhook de MENSAGEM (payload.messages existe)
+function parseIncomingMessage(payload) {
+ const contact = payload.contacts[0];
+ const message = payload.messages[0];
+
+ // Campos NOVOS (sempre presentes a partir de 31/março)
+ const bsuid = contact.user_id; // ← BSUID: sempre
+ const username = contact.profile?.username; // ← se adotou
+ const fromBsuid = message.from_user_id; // ← BSUID: sempre
+
+ // Campos que PODEM ser omitidos (username + sem interação 30d)
+ const phone = contact.wa_id || null; // ← pode ser undefined!
+ const from = message.from || null; // ← pode ser undefined!
+
+ return { bsuid, username, phone, from, fromBsuid };
+}
+
+// Webhook de STATUS (payload.statuses existe — nunca junto com messages)
+function parseStatus(payload) {
+ // contacts[] é NOVO em status webhooks, omitido em status "failed"
+ const contact = payload.contacts?.[0];
+ const status = payload.statuses[0];
+
+ const bsuid = contact?.user_id || null;
+ const recipientBsuid = status.recipient_user_id; // ← BSUID destino
+
+ return { bsuid, recipientBsuid };
+}
+ 4. Resolução dual-identifier
+A estratégia recomendada é phone-first, BSUID-fallback:
+ +function resolveContact(phone, bsuid, username) {
+ let contact = null;
+
+ // 1. Tentar por phone (backward compatible)
+ if (phone) {
+ contact = findByPhone(phone);
+ if (contact && bsuid && !contact.bsuid) {
+ contact.bsuid = bsuid; // Backfill BSUID
+ contact.username = username;
+ save(contact);
+ logMapping(bsuid, phone); // Auditoria
+ }
+ }
+
+ // 2. Fallback: tentar por BSUID
+ if (!contact && bsuid) {
+ contact = findByBsuid(bsuid);
+ if (contact && phone && !contact.phone) {
+ contact.phone = phone; // Backfill phone
+ save(contact);
+ logMapping(bsuid, phone);
+ }
+ }
+
+ // 3. Nenhum encontrado: criar novo
+ if (!contact) {
+ contact = createContact({ phone, bsuid, username });
+ if (phone && bsuid) logMapping(bsuid, phone);
+ }
+
+ return contact;
+}
+ Por que phone-first? O campo to (phone) tem precedência no Send API e sempre funciona. Começando pelo phone, você mantém compatibilidade total com contatos existentes e enriquece com BSUID automaticamente.
5. Handler user_id_update
+Inscreva seu app no campo user_id_update no Meta App Dashboard. Quando receber o webhook, atualize o BSUID no registro do contato:
function handleUserIdUpdate(payload) {
+ for (const update of payload.user_id_update) {
+ const oldBsuid = update.user_id.previous;
+ const newBsuid = update.user_id.current;
+ const phone = update.wa_id || null;
+
+ const contact = findByBsuid(oldBsuid);
+ if (contact) {
+ contact.bsuid = newBsuid;
+ save(contact);
+ logMapping(newBsuid, phone);
+ }
+ }
+}
+ 6. Testar no App Dashboard
+Desde 16/fev/2026, o Meta App Dashboard permite enviar webhooks de teste com BSUIDs. Teste estes cenários:
+-
+
- Cenário 1: Usuário SEM username → BSUID + phone presentes +
- Cenário 2: Usuário COM username, phone indisponível → BSUID + username, sem phone +
- Cenário 3: Usuário COM username, phone disponível → todos os campos +
- Cenário 4: user_id_update → BSUID antigo → BSUID novo +
Checklist de migração
+| Item | Prioridade | Prazo |
|---|---|---|
Parsear contacts[].user_id e messages[].from_user_id dos webhooks | Crítica | Agora |
Adicionar colunas bsuid e username no banco | Crítica | Agora |
Tornar phone_id nullable | Crítica | Agora |
| Implementar resolução dual (phone-first, BSUID-fallback) | Crítica | Primeira semana de abril |
| Backfill automático cruzado | Crítica | Primeira semana de abril |
Handler user_id_update | Alta | Abril |
| UI null-safe (phone_id pode ser null) | Alta | Abril |
| Display @username como fallback | Alta | Abril |
Adaptar Send API (to vs recipient + guard temporal) | Alta | Antes de maio |
| Tratar error 131062 (user error, não retry) | Normal | Antes de maio |
| Configurar monitoring para BSUIDs | Normal | Abril |
| Implementar REQUEST_CONTACT_INFO em templates | Normal | Maio+ |
Referências
+-
+
- Meta BSUID Documentation (atualizada 23/mar/2026) +
- Meta App Dashboard — testes com BSUIDs +
Fonte: Meta Developer Documentation — Business-scoped user IDs. Última atualização do documento fonte: 23 de março de 2026. Qualquer mudança descrita pela Meta está sujeita a alteração.
+ +
+