"

Como habilitar Coexistence no WhatsApp Cloud API: tutorial passo a passo para Tech Provider

Tutorial completo para habilitar a opção "Conectar um app do WhatsApp Business" no Embedded Signup. Permite que o cliente continue usando o WhatsApp Business no celular enquanto a Cloud API espelha as mensagens via webhook.

Jonathan MachadoJonathan Machado
12 min de leitura1.840 palavras

Existe uma opção no Embedded Signup do WhatsApp Cloud API que separa os Tech Providers que sabem o que estão fazendo dos que ficam pra trás: a Coexistence.

É aquela linha extra que aparece no popup do Meta — "Conectar um app do WhatsApp Business" — quando o cliente vai vincular o canal. Sem ela, o cliente que já usa o WhatsApp Business no celular precisa abandonar tudo e migrar pra Cloud API pura. Com ela, ele continua usando o app do celular normalmente e o seu sistema espelha as mensagens via webhook.

É o mesmo recurso que o Manychat oferece. E muita gente acha que é exclusivo de Solution Partner — não é. Qualquer Tech Provider verificado pela Meta consegue habilitar.

Este tutorial é o passo a passo prático que usamos pra habilitar Coexistence no funilchatbot.com.br, em produção, sem quebrar o fluxo legado. Tempo total: cerca de 4 horas.

Pré-requisitos (validar antes)

  • Tech Provider verificado pela Meta (Access Verification aprovada)
  • App Review aprovado com duas permissões em Advanced Access: whatsapp_business_messaging e whatsapp_business_management
  • Business verification completo no Business Manager
  • App em modo Live (não Development)
  • Embedded Signup Configuration criada e em uso
  • O cliente final precisa ter o WhatsApp Business app no celular versão 2.24.17 ou superior

Se o App Review ainda não foi aprovado, Coexistence não funciona. Resolva isso primeiro — sem Advanced Access nas duas permissões, o popup do Meta nem oferece a opção.

Parte 1 — Configurar o painel Meta (cerca de 10 minutos)

1.1. Habilitar Coexistence no Embedded Signup Config

Acesse developers.facebook.com/apps/<APP_ID>/use_cases/ e siga:

  1. Casos de uso → clique em "Personalizar" no card "Conectar-se com clientes pelo WhatsApp"
  2. Menu lateral → Configurador de cadastro incorporado
  3. Role até a seção Diálogo do cadastro incorporado
  4. No dropdown Tipo de recurso · Opcional, selecione: Integração do app WhatsApp Business
  5. Confirme que a Versão do cadastro incorporado está em v4 e a Versão das informações da sessão em 3

1.2. Assinar 3 webhooks adicionais

No painel developers.facebook.com/apps/<APP_ID>/dashboard/, vá ao produto WhatsApp → Configuração. Na seção de webhooks da sua WABA, assine (além de messages que você já tem):

  • history — histórico de mensagens passadas (até 180 dias)
  • smb_app_state_sync — sincronização de contatos do cliente
  • smb_message_echoes — mensagens que o cliente envia pelo app do celular

Importante: esses três webhooks precisam estar assinados no app E no subscribed_apps da WABA. O painel cobre o primeiro lado; o segundo lado é configurado via código (item 2.4 abaixo).

Parte 2 — Implementação no código

2.1. Frontend: alterar o FB.login

A mudança crítica é uma só. Antes:

FB.login(callback, {
  config_id: '<SEU_CONFIG_ID>',
  response_type: 'code',
  override_default_response_type: true,
})

Depois:

FB.login(callback, {
  config_id: '<SEU_CONFIG_ID>',
  response_type: 'code',
  override_default_response_type: true,
  extras: {
    setup: {},
    featureType: 'whatsapp_business_app_onboarding',
    sessionInfoVersion: '3',
  },
})

É o featureType: 'whatsapp_business_app_onboarding' que ativa a Coexistence. Sem ele, o popup do Meta nem oferece a opção. Com ele, a opção "Conectar um app do WhatsApp Business" aparece automaticamente no dropdown de seleção de WABA.

2.2. Frontend: listener postMessage

O evento retornado pelo Meta é diferente. No fluxo padrão você ouve FINISH. Em Coexistence, é FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING. Adapte o listener:

window.addEventListener('message', (event) => {
  if (!event.origin || !/^https:\/\/(.*\.)?facebook\.com$/.test(event.origin)) return

  let data = event.data
  if (typeof data === 'string') {
    try { data = JSON.parse(data) } catch { return }
  }
  if (data?.type !== 'WA_EMBEDDED_SIGNUP') return

  const { event: evt, data: payload } = data

  if (evt === 'FINISH' || evt === 'success') {
    handleStandardFlow(payload)
    return
  }

  if (evt === 'FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING') {
    handleCoexistenceFlow({ ...payload, coexistence: true })
    return
  }

  if (evt === 'CANCEL') {
    console.warn('signup cancelado:', payload)
  }
})

O payload do FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING traz os mesmos campos do FINISH normal: waba_id, phone_number_id, business_id. Você só precisa marcar internamente que aquela conexão é Coexistence pra rotear no backend.

2.3. Backend: aceitar WABA do cliente no auto-discovery

Aqui está um gotcha que pega muita gente. Em Coexistence, a WABA pertence ao cliente, não ao seu Business Manager. Ela aparece em client_whatsapp_business_accounts, não em owned_whatsapp_business_accounts.

Se o seu auto-discovery só olha em owned_* (como o nosso olhava), ele vai retornar "WABA não encontrada" e o fluxo trava. Adicione o fallback:

const meR = await fetch(`${GRAPH}/me/businesses`, {
  headers: { Authorization: `Bearer ${userToken}` }
})
const bms = (await meR.json())?.data || []

for (const bm of bms) {
  // Fluxo padrão: WABA própria
  const ownR = await fetch(`${GRAPH}/${bm.id}/owned_whatsapp_business_accounts`, {
    headers: { Authorization: `Bearer ${userToken}` }
  })
  const owned = await ownR.json()
  if (owned?.data?.length) {
    wabaId = owned.data[0].id
    break
  }

  // Fallback Coexistence: WABA do cliente compartilhada
  const cliR = await fetch(`${GRAPH}/${bm.id}/client_whatsapp_business_accounts`, {
    headers: { Authorization: `Bearer ${userToken}` }
  })
  const client = await cliR.json()
  if (client?.data?.length) {
    wabaId = client.data[0].id
    break
  }
}

2.4. Backend: subscribe app com fields de Coexistence

Quando você chama POST /<WABA_ID>/subscribed_apps com body vazio, a Meta assina só os defaults — basicamente messages. Os três webhooks de Coexistence ficam de fora.

Mude para passar a lista explícita, condicional ao tipo de conexão:

const subscribedFields = coexistence
  ? [
      'messages',
      'message_template_status_update',
      'phone_number_quality_update',
      'message_echoes',
      'smb_app_state_sync',
      'history',
    ]
  : [
      'messages',
      'message_template_status_update',
      'phone_number_quality_update',
    ]

await fetch(`${GRAPH}/${wabaId}/subscribed_apps`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${userToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ subscribed_fields: subscribedFields }),
})

2.5. Backend: pular o POST /register em Coexistence

No fluxo Cloud API padrão você chama POST /<PHONE_NUMBER_ID>/register com messaging_product: 'whatsapp' e um PIN. Em Coexistence, esse número já está registrado no app do celular do cliente. Se você chamar register, a Meta retorna erro.

if (!coexistence) {
  try {
    await fetch(`${GRAPH}/${phoneNumberId}/register`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${userToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        messaging_product: 'whatsapp',
        pin: '000000',
      }),
    })
  } catch (e) { /* idempotente — silent fail */ }
}

2.6. Webhook handler: tratar os 3 fields novos

Aqui está a parte mais crítica e que mais quebra se feita errado. Os três webhooks novos (message_echoes, smb_app_state_sync, history) NÃO devem disparar a IA.

Se você só remove o filtro if (change.field !== 'messages') continue sem adicionar handlers específicos, vai acontecer o seguinte:

  • O dono do número digita uma mensagem no app do celular → vira um echo → seu sistema interpreta como mensagem do cliente → IA responde para o próprio dono. Constrangedor.
  • O histórico chega com 180 dias de mensagens antigas → IA tenta responder cada uma em loop → você queima crédito Anthropic e o cliente recebe 180 mensagens da sua IA.

Trate cada field separadamente:

async function handleMetaWebhook(payload) {
  for (const entry of payload.entry || []) {
    for (const change of entry.changes || []) {

      if (change.field === 'message_echoes' || change.field === 'smb_message_echoes') {
        await handleEcho(change.value || {})
        continue
      }

      if (change.field === 'history') {
        await handleHistoryBackfill(change.value || {})
        continue
      }

      if (change.field === 'smb_app_state_sync') {
        // contatos sincronizados — log/sync conforme necessário
        continue
      }

      if (change.field !== 'messages') continue
      // handler de inbound normal — inalterado
    }
  }
}

async function handleEcho(value) {
  // Salva como outbound, marca conversa como atendida manualmente.
  // NUNCA chama runAIAgent aqui.
}

async function handleHistoryBackfill(value) {
  // Importa mensagens com metadata.source='coexistence_history_backfill'.
  // NUNCA chama runAIAgent aqui.
}

Adicione idempotência em messages também — Coexistence reenvia mensagens em retry mais agressivamente que o fluxo padrão. Um ON CONFLICT (workspace_id, whatsapp_msg_id) DO NOTHING resolve.

Parte 3 — Banco de dados (recomendado)

Adicione uma flag pra identificar canais Coexistence no seu sistema. Você vai precisar dela pra mostrar badge na UI, lógica de envio diferente (throughput limitado, ver limitações abaixo) e relatórios.

ALTER TABLE channels ADD COLUMN IF NOT EXISTS coexistence_mode BOOLEAN DEFAULT false;
ALTER TABLE channels ADD COLUMN IF NOT EXISTS created_via TEXT;

CREATE UNIQUE INDEX IF NOT EXISTS channels_workspace_waba_uniq
  ON channels(workspace_id, waba_id) WHERE waba_id IS NOT NULL;

No INSERT do canal, grave coexistence_mode=true e created_via='embedded_signup_coexistence' quando o fluxo for Coexistence.

Parte 4 — Envio de mídia (gotcha que muita gente esquece)

A Cloud API NÃO aceita base64 em image.link, audio.link ou video.link. Só URL HTTP/HTTPS pública ou media_id obtido via upload.

Se o seu inbox manda mídia hoje como data:image/jpeg;base64,..., a Meta rejeita silenciosamente. Funciona em provider não-oficial como Evolution/Baileys (que aceita base64), mas em Cloud API não.

Duas soluções:

  1. Faça upload da mídia no seu S3/MinIO próprio primeiro, gera uma URL pública, passa essa URL para a Cloud API.
  2. Faça upload pra Meta antes: POST /<PHONE_NUMBER_ID>/media via multipart, recebe um media_id, usa image.id em vez de image.link.

A primeira opção é mais simples se você já tem MinIO/S3 servido por proxy reverso (Caddy, Nginx, Cloudflare). A segunda é mais robusta porque a Meta cacheia a mídia e você não depende da sua infra estar de pé.

Validação E2E

Depois de tudo isso, o teste de validação é direto:

  1. Cliente abre o seu sistema → tela de "Conectar WhatsApp"
  2. Clica "Conectar com Facebook" → popup do Meta abre
  3. No dropdown "Conta do WhatsApp Business", deve aparecer a opção "Conectar um app do WhatsApp Business" entre as outras. Se aparecer, Coexistence está ativo.
  4. Cliente escolhe essa opção → autoriza no celular dele (recebe push notification do WhatsApp Business)
  5. O callback retorna o evento FINISH_WHATSAPP_BUSINESS_APP_ONBOARDING no listener
  6. Backend cria o channel com coexistence_mode=true

Cenários para testar:

  • Manda mensagem pro número do cliente → chega no seu inbox (handler messages)
  • Cliente responde pelo celular → chega como echo (handler message_echoes) → sua IA NÃO responde, conversa marca como atendida
  • Cliente responde pelo seu inbox → mensagem sai e aparece no celular do cliente também

Limitações documentadas pela Meta

Throughput20 mensagens por segundo fixo (não escala como Cloud API pura)
GruposConversas em grupo não sincronizam
Listas de transmissão, view-once, live locationDesabilitam após integração
Dispositivos conectadosMáximo 4 (WhatsApp para Windows e WearOS não suportados)
Histórico inicialAté 180 dias em 3 fases (pode demorar 24h pra sincronizar)
Janela de sincronização24h ou o sistema re-onboarda automaticamente

Documente essas limitações no onboarding do cliente. Cliente que precisa de mais de 20 msg/s vai precisar migrar pra Cloud API pura (sem Coexistence). Cliente que usa muito grupo vai descobrir do jeito errado se você não avisar.

Deadline crítico: 15 de outubro de 2026

A Meta vai descontinuar Embedded Signup v2 e v3 nessa data. O featureType: 'coex' antigo já está deprecado — o nome novo é whatsapp_business_app_onboarding (v4), que é o que este tutorial usa.

Mas se você ainda usa outros featureType da v3 (only_waba_sharing, marketing_messages_lite), eles vão quebrar em outubro. Recomendação prática: planeje a migração v4 completa até agosto de 2026 pra ter buffer pra debug e teste com clientes reais.

Conclusão

Coexistence não é mágica nem privilégio de Solution Partner. É uma feature documentada do Embedded Signup que qualquer Tech Provider verificado consegue habilitar — desde que tenha App Review aprovado com as duas permissões em Advanced Access.

O trabalho prático é cirúrgico: uma config no painel, três webhooks adicionais assinados, uma linha alterada no FB.login, um fallback no auto-discovery, três handlers novos no webhook receiver, idempotência em mensagens, e os ajustes de mídia que mostramos.

O cliente final ganha um onboarding muito mais suave (continua usando o app que ele já conhece) e você ganha um diferencial competitivo que o Manychat e a maioria dos concorrentes brasileiros ainda não oferecem. Vale o trabalho.

Referências

Perguntas frequentes

Coexistence e o mesmo que Solution Partner?

Nao. Solution Partner e um tier comercial da Meta (com linha de credito e faturamento direto), conquistado por convite. Coexistence (WhatsApp Business App Onboarding) e uma feature documentada do Embedded Signup que qualquer Tech Provider verificado com App Review aprovado em Advanced Access consegue habilitar. Sao termos diferentes que a Meta usa em contextos diferentes.

Posso usar Coexistence se meu App Review ainda nao foi aprovado?

Nao. A Meta so libera o featureType whatsapp_business_app_onboarding no Embedded Signup depois que as permissoes whatsapp_business_messaging e whatsapp_business_management estao em Advanced Access. Antes disso o popup nem oferece a opcao Conectar um app. Resolva o App Review primeiro.

O cliente vai perder mensagens ao conectar Coexistence?

Nao perde mensagens novas, mas o historico antigo pode demorar ate 24 horas pra sincronizar (em 3 fases, ate 180 dias). E todos os dispositivos vinculados sao desconectados no momento da integracao e precisam religar (limite de 4 dispositivos, sem suporte a Windows e WearOS). Documente isso no onboarding do cliente.

Por que a IA nao pode responder o webhook message_echoes?

O message_echoes traz as mensagens que o proprio dono do numero digitou no app do celular. Se voce dispara IA nesses eventos, sua IA vai responder o proprio dono como se fosse cliente. Trate echo como outbound, marque a conversa como atendida manualmente, e desative IA na conversa e no contato. NUNCA chame runAIAgent no handler de echo nem no handler de history (backfill).

Coexistence escala para clientes grandes?

Coexistence tem throughput fixo em 20 mensagens por segundo, nao escala. Clientes com volume maior que isso vao precisar migrar pra Cloud API pura (sem o app do celular). Tambem nao sincroniza conversas em grupo, listas de transmissao, mensagens temporarias, view-once nem live location. Use Coexistence para clientes pequenos e medios que querem manter o app de celular operando, e ofereca Cloud API pura para clientes que precisam de volume.