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
403comerror: "connection_not_released", e o campofaltandodiz 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
- No console, abra Números, Conectar número, e toque em Conectar WhatsApp.
- Na janela da Meta, o dono do número escolhe a conta de WhatsApp e o número, e autoriza.
- 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()){
"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_SENT | O registro está em curso, e a plataforma o conduz sozinha. Não abra outra conexão: isso duplicaria trabalho que já está na fila. |
REGISTERED | O número está registrado na Cloud API. |
PIN_REQUIRED | O número exige o PIN da verificação em duas etapas, e a plataforma não o conhece. Veja o PIN. |
BLOCKED | O 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.
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
- No console, abra Números, Conectar número, e toque em Conectar número que já uso no WhatsApp Business.
- 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.
- 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/messagesjunto 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
tetoDeVazaoPorSegundodiz qual caso é o seu: com valor, é o teto fixo; nulo, valethroughputLevel.
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 |
|---|---|
| nulo | Não há importação para este número. |
em_curso | Está acontecendo. sincronizacaoPrazo diz até quando. |
concluida | Terminou, e as conversas antigas já aparecem em GET /v1/messages. |
recusada | O 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
sincronizacaoProgressoquer 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
pintem exatamente seis dígitos, e oidna 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
409comerror: "register_budget_reserved", e os campostentativasRestantesejanelaViradizem 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
409comerror: "pin_not_applicable"e ostatecorrente.
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:
POST /v1/onboarding/sessionsabre a sessão e devolvesessionId,configId,csrfStateeexpiresAt. Enquanto a sessão não é concluída, nenhuma conta existe do lado da Luna.- O dono do número autoriza na janela da Meta, que devolve um código.
POST /v1/onboarding/sessions/{id}/completetroca o código por acesso permanente. OcsrfStatevolta na conclusão e é comparado em tempo constante.GET /v1/onboarding/sessions/{id}mostra o estado e aorientacao.POST /v1/onboarding/sessions/{id}/abandonregistra, 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,phoneNumberIdebusinessIdsã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
200mesmo 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.