LunaDocumentaçãoEntrar

Conectar um número

Há três situações: um número novo para a Cloud API, um número que continua funcionando no aplicativo WhatsApp Business do celular, e um número que já está na Cloud API de outro provedor. Em todas, o número e a conta de WhatsApp Business continuam sendo do negócio final.

Como a conexão funciona

A conexão acontece no fluxo que a Meta chama de Embedded Signup. O dono do número escolhe, na janela da Meta, a conta de WhatsApp e o número dele, e autoriza o acesso na tela de consentimento. A Luna troca o código dessa autorização por acesso permanente, inscreve o aplicativo na conta e registra o número.

  • Não há QR Code. Cada negócio final precisa da própria conta de WhatsApp Business na Meta, com o método de pagamento dele. Veja a diferença de modelo se você vem de uma biblioteca não oficial.
  • A Luna atende só números com código de país do Brasil (+55). Um número de outro país não entra em estado ativo.
  • A conta nova precisa ter o e-mail confirmado para abrir a conexão. Sem isso a API responde 403 com error: "connection_not_released", e o campo faltando diz o que falta.
  • A janela de conexão é aberta pelo console da Luna, no menu Números, em Conectar número. Os endereços autorizados no aplicativo da Meta para essa janela são fixos, e hoje são os do console.

Número novo

  1. No console, abra Números, Conectar número, e toque em Conectar WhatsApp.
  2. Na janela da Meta, o dono do número escolhe a conta de WhatsApp e o número, e autoriza.
  3. Acompanhe pelo console ou pela API. O código da conexão vale 30 segundos, e o console o troca no mesmo instante em que ele chega.
curl -X GET "https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID" \
  -H "Authorization: Bearer $LUNA_API_KEY"
const res = await fetch('https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.LUNA_API_KEY}`,
  },
});
console.log(res.status, await res.json());
import os
import requests

res = requests.get(
    "https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID",
    headers={
        "Authorization": f"Bearer {os.environ['LUNA_API_KEY']}",
    },
    timeout=10,
)
print(res.status_code, res.json())
Resposta (resumida)
{
  "id": "ed404df1-b420-4ed6-8743-ba1609892e0a",
  "metaPhoneNumberId": "123456789012345",
  "displayNumber": "+55 11 99999-0000",
  "state": "REGISTERED",
  "podeRetentar": false,
  "qualityRating": "GREEN",
  "orientacao": "Número conectado e operando. Mensagens já entram e saem por ele.",
  "tetoDeVazaoPorSegundo": null,
  "sincronizacaoDoHistorico": null,
  "subscriptionConfirmedAt": "2026-10-06T11:27:47.141Z",
  "credentialStatus": "active",
  "podeReconectar": false
}

Os estados de um número

`state`O que significa
PENDING, REGISTER_SENTO registro está em curso, e a plataforma o conduz sozinha. Não abra outra conexão: isso duplicaria trabalho que já está na fila.
REGISTEREDO número está registrado na Cloud API.
PIN_REQUIREDO número exige o PIN da verificação em duas etapas, e a plataforma não o conhece. Veja o PIN.
BLOCKEDO número esgotou as tentativas de registro que a Meta concede numa janela de 72 horas. Só resta esperar a janela correr.

O campo orientacao traz, em português, o que está acontecendo e o que fazer. Ele é escrito para você repassar ao seu cliente sem reescrever. O campo podeRetentar diz se o estado admite uma nova tentativa de conexão: a decisão é da plataforma, porque cada tentativa de registro consome uma cota que a Meta conta por número.

Olhe `subscriptionConfirmedAt`. Ele registra quando a inscrição do aplicativo na conta foi confirmada. Enquanto for nulo, nenhum evento chega, e a falha é silenciosa: o número parece perfeito e não recebe nada.

Se a Meta recusar o envio por falta de método de pagamento do negócio final, o número fica registrado e sem enviar. Não é erro da plataforma. A orientacao diz o que falta e onde resolver.

Número que continua no aplicativo do celular (coexistência)

A Meta chama de coexistência o caso de um número que hoje só existe no aplicativo WhatsApp Business do celular. Ele nunca passou por API nenhuma. Conectado assim, o número continua funcionando no aplicativo, e a Luna passa a operá-lo também.

O que você precisa

  • O aplicativo WhatsApp Business (e não o WhatsApp comum) na versão 2.24.17 ou mais nova.
  • A conta do negócio final habilitada para isso do lado da Meta. A opção só aparece na janela quando ela está.
  • Uso real prévio do aplicativo. A Meta recusa, com aviso de atividade insuficiente, um número recém-instalado ou que acabou de sair do WhatsApp comum. A tela de conexão do console recomenda usar o aplicativo por alguns dias, cerca de uma semana, e tentar de novo.

Como conectar

  1. No console, abra Números, Conectar número, e toque em Conectar número que já uso no WhatsApp Business.
  2. Na janela da Meta, escolha a opção de conectar o aplicativo que você já usa, e não a de cadastrar número novo.
  3. O código de confirmação chega como uma mensagem no próprio WhatsApp, no aplicativo do celular.

O que muda depois de conectado

  • As conversas anteriores do aparelho são importadas para a plataforma, e aparecem em GET /v1/messages junto das novas.
  • O que o dono do negócio responde pelo aplicativo do celular também chega, na conversa do destinatário.
  • O envio tem teto fixo de 20 mensagens por segundo por número, e ele não sobe com o tempo. Um número fora da coexistência segue uma progressão que a Meta sobe sozinha conforme a qualidade. O campo tetoDeVazaoPorSegundo diz qual caso é o seu: com valor, é o teto fixo; nulo, vale throughputLevel.

A importação do histórico tem prazo

Passado o prazo sem concluir, a plataforma da Meta desconecta o negócio final, e ele precisa refazer a conexão no aparelho. Quem diz onde a importação está é GET /v1/numbers/{id}, nos campos sincronizacaoDoHistorico e sincronizacaoPrazo:

`sincronizacaoDoHistorico`O que significa
nuloNão há importação para este número.
em_cursoEstá acontecendo. sincronizacaoPrazo diz até quando.
concluidaTerminou, e as conversas antigas já aparecem em GET /v1/messages.
recusadaO dono do negócio recusou compartilhar no aparelho. Não vai concluir, e pedir de novo não muda isso.
  • Não leia o percentual sozinho. sincronizacaoProgresso é um rótulo que a Meta atribui, e 100% não significa sucesso. Quem diz o desfecho é sincronizacaoDoHistorico, e só ele.
  • Nulo em sincronizacaoProgresso quer dizer que não há importação. Zero é uma importação que começou e ainda não andou.
  • sincronizacaoPrazo é a estimativa da plataforma: 24 horas contadas de quando ela viu a importação começar. O relógio da Meta começa um pouco antes, então trate o instante como limite e aja com folga.

Se a importação ainda estiver em curso quando o prazo se aproxima, a Luna manda ao seu webhook o evento history_sync.deadline_approaching. O aviso sai antes do prazo, nunca depois, e é um só por número. Ele pode se repetir, sempre com o mesmo event_id. O estado corrente está em GET /v1/numbers/{id}/health.

{
  "version": "2026-08-25",
  "event_id": "d42ca56e44ef023d52b0a0739dcf3afb0d5054a8b84321c41e432ed6707b043f",
  "event_type": "history_sync.deadline_approaching",
  "occurred_at": "2026-10-07T03:00:00.000Z",
  "phone_number_id": "123456789012345",
  "history_sync": {
    "prazo": "2026-10-07T09:00:00.000Z"
  }
}
curl -X GET "https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID/health" \
  -H "Authorization: Bearer $LUNA_API_KEY"
const res = await fetch('https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID/health', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.LUNA_API_KEY}`,
  },
});
console.log(res.status, await res.json());
import os
import requests

res = requests.get(
    "https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID/health",
    headers={
        "Authorization": f"Bearer {os.environ['LUNA_API_KEY']}",
    },
    timeout=10,
)
print(res.status_code, res.json())

Número que vem de outro provedor

Migrar um número que já está na Cloud API de outro provedor é um caminho suportado. Não exige número novo e não faz você perder o número atual. Se você vem de uma biblioteca não oficial, leia antes o guia de migração da Evolution API: ele explica o que muda no modelo.

Os pré-requisitos são do lado do negócio final

  • Ser o dono da conta de WhatsApp Business.
  • Ter o número liberado no provedor anterior.
  • Conhecer o PIN da verificação em duas etapas, ou desligar essa verificação no WhatsApp Manager dele.

O PIN

Um número já registrado na Cloud API de outro provedor quase sempre tem verificação em duas etapas configurada lá, e a Meta exige o PIN existente para registrá-lo de novo. Não há endpoint para ler nem para desligar esse PIN, então ele precisa vir do negócio final. Quando o número fica em PIN_REQUIRED, informe o PIN:

curl -X POST "https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID/pin" \
  -H "Authorization: Bearer $LUNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "pin": "123456"
}'
const res = await fetch('https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID/pin', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LUNA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "pin": "123456"
  }),
});
console.log(res.status, await res.json());
import os
import requests

res = requests.post(
    "https://api.lunahia.com.br/v1/numbers/YOUR_NUMBER_ID/pin",
    headers={
        "Authorization": f"Bearer {os.environ['LUNA_API_KEY']}",
    },
    json={
        "pin": "123456",
    },
    timeout=10,
)
print(res.status_code, res.json())
  • O pin tem exatamente seis dígitos, e o id na URL é o da Luna, não o da Meta.
  • O PIN é guardado cifrado e nunca volta por rota nenhuma.
  • Um PIN errado é recusado pela Meta e consome uma das dez tentativas de registro que ela concede por número numa janela de 72 horas. Esgotar essas tentativas trava o número até a janela correr.
  • Por isso a plataforma guarda as últimas. Perto do fim, a rota responde 409 com error: "register_budget_reserved", e os campos tentativasRestantes e janelaVira dizem quantas sobram e quando a janela vira. Confirme o PIN com o negócio final antes de tentar de novo.
  • Um número que não está aguardando o PIN responde 409 com error: "pin_not_applicable" e o state corrente.

Quando a autorização expira

credentialStatus diz se a autorização do cliente continua válida. Quando ela morre, o número precisa ser reconectado. Se credentialStatus for reauth_required e podeReconectar for verdadeiro, abra uma sessão de reconexão:

curl -X POST "https://api.lunahia.com.br/v1/onboarding/reconnect" \
  -H "Authorization: Bearer $LUNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "numberId": "00000000-0000-4000-8000-000000000000"
}'
const res = await fetch('https://api.lunahia.com.br/v1/onboarding/reconnect', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LUNA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "numberId": "00000000-0000-4000-8000-000000000000"
  }),
});
console.log(res.status, await res.json());
import os
import requests

res = requests.post(
    "https://api.lunahia.com.br/v1/onboarding/reconnect",
    headers={
        "Authorization": f"Bearer {os.environ['LUNA_API_KEY']}",
    },
    json={
        "numberId": "00000000-0000-4000-8000-000000000000",
    },
    timeout=10,
)
print(res.status_code, res.json())

A sessão devolvida é usada exatamente como a de uma conexão nova, e a conclusão substitui a autorização daquela conta em vez de criar uma segunda. Um número cuja autorização ainda é válida responde 409 com error: "reconnect_not_applicable" e o credentialStatus corrente. Autorização revogada em definitivo também responde 409, e o caminho é uma conexão nova.

As rotas de onboarding

O console é cliente desta mesma API, sem rota privilegiada. Se você precisa entender o que ele faz, o ciclo de uma conexão é este:

  1. POST /v1/onboarding/sessions abre a sessão e devolve sessionId, configId, csrfState e expiresAt. Enquanto a sessão não é concluída, nenhuma conta existe do lado da Luna.
  2. O dono do número autoriza na janela da Meta, que devolve um código.
  3. POST /v1/onboarding/sessions/{id}/complete troca o código por acesso permanente. O csrfState volta na conclusão e é comparado em tempo constante.
  4. GET /v1/onboarding/sessions/{id} mostra o estado e a orientacao. POST /v1/onboarding/sessions/{id}/abandon registra, como telemetria, em que tela o fluxo parou.
curl -X POST "https://api.lunahia.com.br/v1/onboarding/sessions" \
  -H "Authorization: Bearer $LUNA_API_KEY"
const res = await fetch('https://api.lunahia.com.br/v1/onboarding/sessions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LUNA_API_KEY}`,
  },
});
console.log(res.status, await res.json());
import os
import requests

res = requests.post(
    "https://api.lunahia.com.br/v1/onboarding/sessions",
    headers={
        "Authorization": f"Bearer {os.environ['LUNA_API_KEY']}",
    },
    timeout=10,
)
print(res.status_code, res.json())
curl -X POST "https://api.lunahia.com.br/v1/onboarding/sessions/YOUR_SESSION_ID/complete" \
  -H "Authorization: Bearer $LUNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "YOUR_CODE_FROM_META",
  "csrfState": "YOUR_CSRF_STATE"
}'
const res = await fetch('https://api.lunahia.com.br/v1/onboarding/sessions/YOUR_SESSION_ID/complete', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LUNA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "code": "YOUR_CODE_FROM_META",
    "csrfState": "YOUR_CSRF_STATE"
  }),
});
console.log(res.status, await res.json());
import os
import requests

res = requests.post(
    "https://api.lunahia.com.br/v1/onboarding/sessions/YOUR_SESSION_ID/complete",
    headers={
        "Authorization": f"Bearer {os.environ['LUNA_API_KEY']}",
    },
    json={
        "code": "YOUR_CODE_FROM_META",
        "csrfState": "YOUR_CSRF_STATE",
    },
    timeout=10,
)
print(res.status_code, res.json())
  • O código vale 30 segundos. Chame a conclusão no mesmo instante em que ele chega, sem fila, sem trabalho intermediário e sem partida a frio no caminho.
  • wabaId, phoneNumberId e businessId são opcionais. Quando o navegador não os entrega, a Luna os descobre na Graph API a partir do próprio token. Mande o que tiver.
  • A conclusão responde 200 mesmo quando um passo falha. O corpo traz o estado real (state, terminal, motivo, podeRetentar, proximoPasso), porque conexão que para no meio é estado de negócio, e não erro de requisição.
  • As rotas de onboarding pedem o escopo onboarding:write.
Para ver cada campo, abra a seção Onboarding na referência da API.