Migrar da Evolution API para a Luna
A Evolution API é uma camada sobre biblioteca não-oficial; a Luna opera exclusivamente sobre a WhatsApp Cloud API oficial da Meta, na condição de Tech Provider aprovado. As duas fazem coisas parecidas por caminhos diferentes, e a diferença de caminho é o assunto deste documento.
Leia as três diferenças abaixo antes da tabela. Elas são o que quebra uma migração no meio: quem começa pela tabela descobre a primeira delas no primeiro cliente que responde no dia seguinte, com o desenho já pronto.
Três coisas que vão mudar o seu desenho
1. A janela de 24 horas existe, e não há como contorná-la
Na Cloud API oficial, texto livre só sai para quem falou com você nas últimas 24 horas. Passado esse prazo, o único caminho é um template aprovado pela Meta. Numa biblioteca não-oficial essa restrição não aparece, porque ela conversa pelo mesmo canal de um aparelho. O desenho que funcionava lá presume uma liberdade que aqui não existe.
Leia `GET /v1/conversations` antes de decidir como responder. Cada linha traz `windowOpen`, `windowExpiresAt` e `windowSource`: a plataforma avisa **antes** de você gastar a chamada, em vez de devolver um erro depois. Com a janela fechada, o caminho é enviar um template, e ele reabre a conversa.
2. O envio responde `202`, e o identificador da Meta não vem junto
O `POST /v1/messages` devolve `202` com um identificador da Luna e o estado `queued`. Ele não devolve o `wamid`, porque nesse instante a Meta ainda não o emitiu. Quem integrou esperando o identificador definitivo na resposta precisa mudar o desenho, e essa é a segunda coisa que quebra uma migração.
A razão é honesta e vale saber: o limite que mais aperta na Cloud API é de uma mensagem a cada seis segundos para o mesmo destinatário. Um agente que responde em rajada dispara esse limite sozinho, e a resposta certa a isso é fila, nunca um erro devolvido a você. Guarde o identificador da Luna, e receba o `wamid` e o estado de entrega pelo seu webhook, no evento `message.status`.
3. Não há QR Code, e não pode haver
Conectar um número lendo um QR Code com o aparelho é o modelo de uma biblioteca que se apresenta como um WhatsApp Web. A plataforma oficial não funciona assim: cada negócio final precisa da própria conta de WhatsApp Business na Meta, com o método de pagamento dele, e a conexão acontece pelo fluxo que a Meta chama de Embedded Signup: dentro do seu produto, mas contra a Meta.
Esta é a diferença que **não tem contorno**, e ela é a razão de a Luna existir na forma que existe. É também o que separa uma conexão que sobrevive de uma que é desligada: o número é do seu cliente, na conta dele, e a Helsen opera como Tech Provider aprovado pela Meta. O ciclo começa em `POST /v1/onboarding/sessions` e termina em `POST /v1/onboarding/sessions/{id}/complete`.
Capacidade a capacidade
O mapa é por capacidade, e não por assinatura de rota: prometer que uma chamada vira outra seria prometer paridade, e não é o caso. Onde não há equivalente, a razão está escrita, e quase sempre ela é uma regra da plataforma oficial, não uma escolha da Luna.
| Capacidade | Na Evolution API | Na Luna | O que saber |
|---|---|---|---|
| Enviar uma mensagem de texto | Uma rota de envio de texto por instância, respondendo com o resultado do envio. | POST /v1/messages | O corpo traz `from`, `to` e `text.body`. A resposta é `202` com o identificador da Luna e o estado `queued`; o `wamid` chega depois, pelo webhook. Um corpo sem `type` é interpretado como texto. |
| Enviar imagem, áudio, vídeo ou documento | Uma rota de envio de mídia por instância, aceitando o arquivo em base64 ou por URL. | POST /v1/numbers/{id}/media, depois POST /v1/messages | São dois passos porque a Meta trabalha com identificador de mídia. Suba o arquivo em `multipart/form-data`, receba o identificador e use-o no envio. Enviar por `link` também funciona, e nesse caso a URL precisa responder sem autenticação. O identificador evita isso e pode ser reaproveitado em várias mensagens. |
| Iniciar conversa fora da janela de 24 horas | Normalmente não existe como conceito: o envio de texto vale a qualquer momento. | POST /v1/templates, depois POST /v1/messages com `type: template` | É a consequência direta da primeira diferença de modelo. O template é criado, submetido à Meta e usado depois de aprovado, e o estado dele chega por webhook, sem que você precise ficar consultando. |
| Receber o que chega no número | Um webhook configurado por instância, com os eventos escolhidos na configuração. | POST /v1/webhooks/endpoints | Um endpoint para o tenant inteiro, e não por número: os eventos dizem de que número vieram. Toda entrega é assinada, e o segredo pode ser rotacionado sem janela de indisponibilidade por `POST /v1/webhooks/endpoints/{id}/rotacionar`, que mantém o segredo anterior válido por um período. |
| Recuperar o que o seu servidor não recebeu | Depende da instalação; frequentemente não há retentativa nem registro do que falhou. | GET /v1/webhooks/dlq, POST /v1/webhooks/dlq/{id}/reenfileirar | Um evento chega à fila de rejeitados depois de a política de retentativa se esgotar. Ele não é entregue de novo sozinho: ou você o re-enfileira, ou ele fica lá até a retenção expirar. A resposta não traz total, de propósito: para saber quantos itens há, pagine até `nextCursor` vir nulo. |
| Consultar o histórico de mensagens | Uma consulta ao armazenamento da própria instância, com filtros por chat. | GET /v1/messages, GET /v1/conversations | Filtre por `phoneNumberId` ou por `conversationId`, e pagine com `nextCursor`. O cursor é opaco: ele carrega posição e não contagem, então não tente construir um à mão. |
| Saber se a mensagem foi entregue e lida | Eventos de atualização de status entregues pelo webhook da instância. | Evento `message.status` no seu webhook, e GET /v1/messages/{id} | O estado chega por evento, e a consulta por identificador serve para reconciliar. É por aqui que o `wamid` da Meta se liga ao identificador que o `202` devolveu no envio. |
| Baixar a mídia que o cliente enviou | A mídia costuma vir embutida no evento, ou é buscada no armazenamento da instância. | GET /v1/messages/{id}/media | A Luna guarda o arquivo e o serve por esta rota. A URL de download que a Meta emite expira em cinco minutos, e guardar essa URL é a forma mais comum de uma retentativa falhar sempre. Por isso ela nunca é repassada. |
| Marcar como lida e mostrar “digitando” | Rotas de presença e de leitura por instância. | POST /v1/messages/{id}/read, POST /v1/messages/{id}/typing | Existem e funcionam, e são o que faz um agente automático parecer atendimento e não robô. Ambas agem sobre a mensagem recebida, e não sobre a conversa. |
| Conectar um número novo | Criar uma instância e ler o QR Code com o aparelho que tem o número. | POST /v1/onboarding/sessions e o fluxo Embedded Signup | **Sem equivalente, e é a diferença que não tem contorno.** O onboarding é Embedded Signup porque cada negócio final precisa da própria conta de WhatsApp Business e do próprio método de pagamento na Meta, que é a condição de operar como Tech Provider. O número é do seu cliente, na conta dele, e é isso que faz a conexão sobreviver. |
| Listar os números conectados e o estado de cada um | Uma listagem de instâncias, com o estado da conexão de cada uma. | GET /v1/numbers, GET /v1/numbers/{id} | O identificador da Luna e o da Meta são campos distintos, e a distinção importa: as rotas de envio pedem o da Meta (`metaPhoneNumberId`), e as de mídia e detalhe pedem o da Luna. |
| Criar grupo, entrar em grupo, mandar para grupo | Rotas de grupo, herdadas de a biblioteca se apresentar como um aparelho. | Sem equivalente | **Sem equivalente.** A Cloud API oficial não expõe grupos a um número de negócio: o produto que a Meta publica é a conversa entre uma empresa e uma pessoa. Não é uma escolha da Luna, e nenhuma plataforma oficial oferece isso. |
| Ler a agenda, o perfil e a foto de quem escreveu | Rotas de contato e de perfil, pela mesma razão da linha acima. | Sem equivalente | **Sem equivalente.** Um número de negócio na plataforma oficial não tem agenda, e o perfil de quem escreve não é publicado. O que chega é o telefone do interlocutor e o nome que ele mesmo expôs no evento, e é sobre isso que dá para trabalhar. |
| Verificar se um número tem WhatsApp antes de mandar | Uma rota de verificação, respondendo se o número existe na rede. | Sem equivalente | **Sem equivalente.** A plataforma oficial não publica essa consulta. O envio para um número sem WhatsApp simplesmente não é entregue, e você fica sabendo pelo evento de status, que é a informação equivalente, um passo depois. |
| Autenticar as suas chamadas | Uma chave global da instalação, normalmente num cabeçalho próprio. | POST /v1/api-keys | A chave vai em `Authorization: Bearer hlsn_…` e carrega **escopos**: uma rota fora do conjunto responde `403`. Dê a cada chave só o que ela precisa: uma que só envia mensagem não deveria poder conectar número. Ela é mostrada uma única vez, e não há rota que a devolva depois. |
O que a Helsen cobra, e o que a Meta cobra
A assinatura da Helsen é pela capacidade de números ativos do seu plano, e por nada além disso. O uso da plataforma WhatsApp é cobrado pela Meta, diretamente do seu cliente final, no método de pagamento que ele cadastra na conta dele. A Helsen não intermedeia esse pagamento e não o repassa. É por isso que cada negócio final precisa de conta própria na Meta.
Por onde começar
A documentação da API traz o contrato de cada rota, com exemplos, e há um cliente TypeScript gerado dela, o mesmo que o console da Helsen usa. Se algo neste guia não bater com a documentação, é a documentação que vale: ela é derivada do código que valida as requisições.
WhatsApp e Meta são marcas da Meta Platforms, Inc. Evolution API é projeto de terceiros, sem relação com a Helsen. Este documento descreve o modelo de cada plataforma para quem está migrando, e não é comparação comercial.