wappmeow wappmeow docs

Documentação

Guia completo da API REST do wappmeow e do SDK oficial. A API roda em /v1, responde JSON e transmite eventos por SSE. Respostas de sucesso vêm em { "data": ... } e erros em { "error": { "code", "message" } }.

Introdução

O wappmeow conecta números de WhatsApp como dispositivos vinculados (multidevice), igual ao WhatsApp Web: o celular não precisa ficar online depois do pareamento. Cada número é uma sessão; você cria sessões, pareia, envia/recebe mensagens e escuta eventos — tudo por HTTP.

  • Base URL (produção): https://web.lagoshost.com.br — a URL oficial, já embutida no SDK. Em desenvolvimento local, use http://localhost:3000.
  • Versão da API: prefixo /v1 (ex.: GET /v1/sessions).
  • Saúde: GET /health — público, retorna nome, versão e grupos de recursos.
  • JIDs: usuários são 5511999999999@s.whatsapp.net, grupos ...@g.us, canais ...@newsletter, LIDs ...@lid.
curl https://web.lagoshost.com.br/health
# { "name": "wappmeow", "version": "...", "features": { ... } }

Instalação & servidor

Requer Node ≥ 22. No diretório do projeto:

npm install
npm start

A configuração vai num arquivo .env na raiz do projeto — copie o modelo .env.example e ajuste. O servidor carrega o .env automaticamente (via dotenv), então funciona igual com npm start ou rodando node src/server.js direto:

cp .env.example .env
# edite o .env: PORT, HOST, WAPPMEOW_STORE_KEY...
npm start

Variáveis de ambiente principais:

VariávelPara quê
PORT / HOSTPorta e endereço de escuta (padrão 3000 em 127.0.0.1).
WAPPMEOW_STORE_KEYChave de 32 bytes (hex de openssl rand -hex 32) que criptografa os bundles das sessões em disco. Sem ela, as sessões vivem só em memória e as rotas de pareamento não iniciam.
WAPPMEOW_DEVICE_STORE_DIRDiretório dos dispositivos persistidos (ex.: ./data/devices).
Produção: sirva atrás de um reverse proxy com HTTPS (Nginx/Caddy) e exponha só em rede confiável — veja Autenticação.

Autenticação

Não há API token. Por design, a API é aberta — qualquer cliente que alcança o servidor pode usar as rotas /v1/*. Isso simplifica bots e integrações internas, mas significa que quem protege o acesso é a sua rede: firewall, VPN ou reverse proxy autenticado na frente. Nunca exponha a porta crua na internet.

SDK oficial (zero-config)

O pacote @eduh_dev021/wappmeow-sdk é um cliente TypeScript da REST API (ESM + CommonJS + tipos). A URL de produção já está embutida no módulo — em produção você não configura nada:

import { WappMeowSDK } from '@eduh_dev021/wappmeow-sdk';

const sdk = new WappMeowSDK(); // produção: https://web.lagoshost.com.br (embutida) 🐱
// dev local: new WappMeowSDK({ baseUrl: 'http://localhost:3000' })

const { session } = await sdk.createSession({ label: 'meu-bot' });
session.on('message', (m) => console.log(m.kind, m.text ?? m.decoded));
await session.messages.text('5511999999999@s.whatsapp.net', 'oi! 🐾');

Namespaces do handle de sessão: pair, messages, media, groups, users, presence, privacy, profile, chats, status, newsletters, appState, account, bots, broadcastLists, calls, labels, commerce, store, server, wam, mobile e events (SSE) — espelho 1:1 das rotas abaixo. A constante exportada DEFAULT_BASE_URL vale https://web.lagoshost.com.br. Erros viram WappMeowApiError com code/status do servidor.

Teste rápido (ping)

Pra validar servidor, sessão e pareamento sem escrever um bot inteiro:

await sdk.ping();            // { ok, latencyMs, health }
await session.ping();        // sessão conectada/pareada?
await session.ping(jid);     // + envia mensagem de teste
session.enablePingPong(); // mande "ping" no WhatsApp → "🏓 pong"

No pacote do SDK há o exemplo pronto: npm run ping (aceita SESSION_ID, TO e PONG=1). E o script de pareamento npm run pair:phone -- +5511999999999 liga o ping-pong sozinho logo após o login (PONG=0 desliga).

Gerenciar sessões

POST/v1/sessions

Cria uma sessão. Corpo opcional: { "label": "meu-bot", "browser": "chrome-ubuntu", "os": "Ubuntu", "waVersion": "auto" }. Sem browser, a identidade é a nativa do whatsmeow (igual ao framework Go); waVersion escolhe a versão do WhatsApp Web gravada no pareamento ("auto" = mais recente, via GET /wa-web-version; ou defina WAPPMEOW_WA_VERSION no servidor). Opções v0.8: ignoredJids (suprime o evento message para JIDs exatos ou sufixo @servidor), syncFullHistory: false (não baixa o histórico completo) e proxyUrl (proxy HTTP(S) só desta sessão). Retorna 201 com o id gerado.

GET/v1/sessions

Lista as sessões (id, label, status, jid, pushName…).

GET/v1/sessions/:id

Detalhes de uma sessão.

DELETE/v1/sessions/:id

Remove a sessão e apaga os dados locais dela.

POST/v1/sessions/:id/connect

Abre o transporte (login se já pareada; fluxo de pareamento se não).

POST/v1/sessions/:id/disconnect

Fecha o transporte sem desvincular.

POST/v1/sessions/:id/logout

Desvincula o dispositivo da conta (remove-companion-device) e desconecta.

POST/v1/sessions/:id/passive

Marca o cliente passivo/ativo: { "passive": true }.

Estados: unpaired → pairing_pending → connected/authenticated. Rotas de recurso retornam 409 enquanto a sessão não está pareada/conectada.

Pareamento (QR · código · passkey)

POST/v1/sessions/:id/pair/qr

Inicia o fluxo de QR e retorna o conteúdo do QR atual (acompanhe rotações pelo evento qr-rotated / pairing-code* no SSE).

POST/v1/sessions/:id/pair/code

Pareamento por código de 8 dígitos: { "phoneNumber": "5511999999999" } (opcionais: customPairCode, clientDisplayName). Renovação automática enquanto pendente. A notificação no celular ("um dispositivo quer se conectar") é pedida com showPushNotification: true (padrão), mas o WhatsApp pode segurá-la (app aberto, vários pareamentos seguidos, bateria) — nesse caso é só abrir Dispositivos conectados > Conectar > com número e digitar o código.

DELETE/v1/sessions/:id/pair/code

Cancela o pedido de código pendente.

GET/v1/sessions/:id/pair/passkey/options

Opções WebAuthn do fluxo de pareamento por passkey.

POST/v1/sessions/:id/pair/passkey/response

Envia a assertion da passkey: { "assertion": { ... } }.

POST/v1/sessions/:id/pair/passkey/confirm

Confirma o handoff da passkey após o evento pair-passkey-confirmation.

Enviar mensagens

Tudo passa por um único endpoint com conteúdo tipado — ou escape total com custom/rawMessageBase64:

POST/v1/sessions/:id/messages

Campos: to (JID) + um de text, media, poll, location, contact, interactive/buttons/list, forward, album, product, order, groupInvite, buttonReply, listReply, event, pin, keep, limitSharing, sharePhoneNumber, requestPhoneNumber, custom, rawMessageBase64. Extras: quoted, mentions[], linkPreview (true = OpenGraph automático), waitForAckMs.

curl -X POST https://web.lagoshost.com.br/v1/sessions/$ID/messages -d '{
  "to": "5511999999999@s.whatsapp.net",
  "text": "Olá do wappmeow! 🐾"
}'
# mídia:  { "to": ..., "media": { "kind": "image", "dataBase64": "...", "caption": "foto" } }
# enquete:{ "to": ..., "poll": { "question": "Pizza hoje?", "options": ["sim", "não"] } }
# custom: { "to": ..., "custom": { "eventMessage": { "name": "Festa", "startTime": 1735689600 } } }

Botões, mídia especial e mais (mesmo endpoint)

# Botões NativeFlow (tipos: reply, url, call, copy, list, location):
# { "to": ..., "interactive": { "text": "Escolha uma opção:", "footer": "toque em um botão",
#     "buttons": [
#       { "type": "reply", "text": "Cardápio", "id": "menu" },
#       { "type": "url",   "text": "Abrir site", "url": "https://..." },
#       { "type": "copy",  "text": "Copiar cupom", "copyCode": "GATO10" } ] } }
# O clique volta no SSE como message kind "interactiveResponse" (nativeFlowReply.id).
#
# Botões legados: { "buttons": { "text": "...", "buttons": [{ "id", "text" }] } }  (máx. 3)
# Lista legada:   { "list": { "title", "text", "buttonText", "sections": [...] } }
#
# Mídia view-once: { "media": { "kind": "image", "dataBase64": "...", "viewOnce": true } }
# Encaminhar:      { "forward": { "rawBase64": "<msg.rawBase64 do evento>" } }
# Álbum:           { "album": { "media": [ { "kind": "image", "dataBase64": "..." }, ... ] } }
# Evento:          { "event": { "name": "Festa", "startTime": 1735689600 } }
# Link preview:    { "text": "veja https://...", "linkPreview": true }
POST/v1/sessions/:id/messages/react

Reação: { "to", "messageId", "emoji" } (emoji vazio remove).

POST/v1/sessions/:id/messages/edit

Edita mensagem própria: { "to", "messageId", "text" }.

POST/v1/sessions/:id/messages/revoke

Apaga para todos: { "to", "messageId", "fromMe": true, "participant?" }.

POST/v1/sessions/:id/messages/poll-vote

Vota numa enquete (payload do SDK: PollVoteInput — chave da enquete, texto das opções; seleção vazia = remover voto).

POST/v1/sessions/:id/messages/read

Marca como lidas: { "chat", "ids": [...], "sender?" }.

POST/v1/sessions/:id/messages/pin

Fixa/desfixa pra todos: { "to", "messageId", "fromMe?", "durationSecs?" } ("unpin": true desfixa). messages/keep mantém mensagem temporária; messages/limit-sharing liga/desliga a restrição de encaminhar/copiar do chat.

POST/v1/sessions/:id/messages/event-respond

RSVP criptografado num evento de calendário: { "to", "messageId", "response": "going"|"notGoing"|"maybe", "extraGuestCount?" }. Respostas de outros chegam no evento SSE event-response.

POST/v1/sessions/:id/messages/share-phone

Compartilha seu número num chat por username/LID (messages/request-phone pede o do outro). messages/poll-aggregate soma votos descriptografados ({ "chat", "pollId", "votes" }).

POST/v1/sessions/:id/url-info

OpenGraph de uma URL (título/descrição/miniatura) pronto pra usar como linkPreview: { "url" } ou { "text" } (usa a 1ª URL do texto).

Recebendo

Mensagens recebidas chegam pelo SSE já descriptografadas: { id, chat, sender, fromMe, kind, text, media?, poll?, reaction?, location?, contact?, quoted, mentions, decoded? }. Tipos "exóticos" (evento, lista, botões, álbum…) vêm com o objeto completo em decoded — nada vira unknown opaco.

Mídia (upload/download)

POST/v1/sessions/:id/media/upload

Sobe bytes (dataBase64 + kind) e retorna o descritor pronto pra anexar em messages. Metadados (dimensões/duração/waveform/thumbnail) são calculados automaticamente no envio se omitidos.

POST/v1/sessions/:id/media/download

Baixa e descriptografa a partir do descritor media de uma mensagem recebida; retorna dataBase64.

POST/v1/sessions/:id/media/retry-receipt

Pede re-upload quando o download falha com 404/410 (mídia antiga): { "id", "chat", "mediaKey", "fromMe?", "sender?" }. A resposta chega no evento media-retry.

Presença & digitando

POST/v1/sessions/:id/presence

Define sua presença (available/unavailable).

POST/v1/sessions/:id/presence/subscribe

Assina a presença de um JID (atualizações via evento presence).

POST/v1/sessions/:id/chatstate

“Digitando/gravando”: composing/recording/paused por chat.

Grupos & comunidades

POST/v1/sessions/:id/groups

Cria grupo: { "name", "participants": [jid...] } (emite joined-group).

GET/v1/sessions/:id/groups

Grupos dos quais a conta participa.

POST/v1/sessions/:id/groups/metadata

Metadados completos (participantes, admins, ephemeral, comunidade pai…).

POST/v1/sessions/:id/groups/participants

add/remove/promote/demote participantes.

POST/v1/sessions/:id/groups/join

Entra por código/link de convite; invite-info mostra prévia, invite-link obtém/revoga o link, leave sai do grupo.

POST/v1/sessions/:id/groups/subject

Assunto (nome) do grupo. Também: description, settings (announce/locked), photo.

POST/v1/sessions/:id/groups/requests

Pedidos de entrada pendentes; requests/update aprova/rejeita (modo de aprovação).

POST/v1/sessions/:id/groups/link

Vincula/desvincula grupo a uma comunidade; subgroups lista os subgrupos e participantes vinculados.

Mudanças ao vivo chegam no evento group (e picture para foto).

Canais (newsletters)

POST/v1/sessions/:id/newsletters/info

Metadados do canal por JID (ou via convite no SDK).

GET/v1/sessions/:id/newsletters/subscribed

Canais seguidos.

POST/v1/sessions/:id/newsletters

Cria canal: { "name", "description?", "pictureBase64?" }.

POST/v1/sessions/:id/newsletters/follow

Segue/deixa de seguir (follow: true/false). Ao seguir, a sessão assina os live updates.

POST/v1/sessions/:id/newsletters/messages

Posts do canal (count, before serverId). Posts novos chegam como mensagem plaintext e em newsletter-live-update.

POST/v1/sessions/:id/newsletters/viewed

Marca posts como vistos: { "jid", "serverIds": [...] }.

POST/v1/sessions/:id/newsletters/reaction

Reage a um post: { "jid", "serverId", "reaction": "👍" } (vazio remove).

POST/v1/sessions/:id/newsletters/mute

Silencia/dessilencia o canal.

POST/v1/sessions/:id/newsletters/updates

Atualizações de contadores (views/forwards/reações): { "jid", "count?", "sinceSeconds?", "after?" }. update altera metadados do canal (admin).

Postar no próprio canal usa POST /messages com to = JID do canal.

Status

POST/v1/sessions/:id/status

Publica status (texto/mídia) com lista de privacidade opcional.

GET/v1/sessions/:id/status/privacy

Configuração atual de privacidade do status.

Conta, perfil & privacidade

POST/v1/sessions/:id/profile/name

Seu nome de exibição (push name). Também: profile/about (recado) e profile/picture (foto).

POST/v1/sessions/:id/users/check

Verifica quais números têm WhatsApp (usync). users/info traz sobre/foto/dispositivos; users/profile-picture a foto; users/business-profile o perfil comercial; users/resolve-username/usernames resolvem @usernames.

GET/v1/sessions/:id/privacy

Configurações de privacidade; POST altera uma categoria. Mudanças externas chegam em privacy-settings.

GET/v1/sessions/:id/blocklist

Lista de bloqueados; POST bloqueia/desbloqueia. Mudanças chegam em blocklist.

POST/v1/sessions/:id/account/email

E-mail da conta: GET consulta, POST define, email/verify confirma o código.

POST/v1/sessions/:id/contact-qr/resolve

Resolve um link wa.me/qr/<code> para { jid, pushName, type }; POST /contact-qr obtém (ou revoga) o seu próprio código.

POST/v1/sessions/:id/disappearing-timer

Mensagens temporárias: com chat ajusta a conversa; sem chat, o padrão da conta. seconds: 0 desliga.

Chats (app state)

POST/v1/sessions/:id/chats/pin

Fixa/desafixa (pin). Irmãs: chats/archive, chats/mute, chats/read, chats/delete, chats/star (favoritar mensagem) e chats/contact (salvar contato na agenda sincronizada).

Bots (Meta AI)

GET/v1/sessions/:id/bots

Lista de bots disponíveis (Meta AI e parceiros).

POST/v1/sessions/:id/bots/profile

Perfil de um bot/persona.

POST/v1/sessions/:id/bots/prompt

Envia um prompt (direto ou por menção em grupo). As respostas chegam em chunks e são descriptografadas automaticamente (botChunk nas mensagens do SSE).

Listas de transmissão

Listas de transmissão business (app state business_broadcast_list):

POST/v1/sessions/:id/broadcast-lists

Cria/atualiza: { "listId", "name?", "participants": [...] }. broadcast-lists/remove apaga.

POST/v1/sessions/:id/broadcast-lists/send

Dispara para a lista (fan-out por sender key para <listId>@broadcast).

Chamadas

A sinalização chega no SSE como call (e por ação: call-offer, call-accept, call-reject, call-terminate), com a stanza bruta para add-ons.

POST/v1/sessions/:id/calls/reject

Rejeita uma chamada recebida: { "from", "callId" }.

POST/v1/sessions/:id/calls/link

Cria link de chamada (call.whatsapp.com): { "type": "video"|"audio", "startTime?" } → { token, type, link }.

Áudio de verdade: o pacote opcional @eduh_dev021/wappmeow-voip implementa call key E2EE + RTP/SRTP (RFC 3711) e o VoipManager — a mídia contra os relays reais do WhatsApp ainda é experimental.

App State & sincronização

POST/v1/sessions/:id/appstate/fetch

Sincroniza uma coleção (regular, critical_unblock_low…) sob demanda. Mutações chegam em appstate e nos eventos granulares (pin, archive, mute, star, contact…).

History sync (histórico do pareamento) é processado automaticamente: conversas, contatos, nomes e mapeamento LID↔PN chegam em history-sync e alimentam os caches da sessão.

Business: etiquetas, catálogo & store

Etiquetas do WhatsApp Business vivem no app state (label_edit/label_association) e agora são editáveis:

POST/v1/sessions/:id/labels

Cria etiqueta: { "id?", "name", "color?" } (201). labels/edit renomeia/troca cor; labels/delete apaga.

POST/v1/sessions/:id/labels/chat

Associa ao chat: { "chatJid", "labelId", "labeled?" } (false remove). labels/message faz o mesmo por mensagem (+ "messageId").

Catálogo/comércio (IQs w:biz:catalog, igual ao Baileys):

GET/v1/sessions/:id/commerce/catalog?jid=&limit=&cursor=

Produtos de uma loja (com nextPageCursor). commerce/collections lista coleções; commerce/orders/:orderId?token= abre um pedido.

POST/v1/sessions/:id/commerce/products

Cria produto no seu catálogo: { "name", "price", "currency", "images": [{ "url" } | { "dataBase64" }], ... }. commerce/products/update (+ "productId"), commerce/products/delete ({ "productIds" }) e commerce/images (sobe a foto do produto, sem criptografia).

A store sincronizada (history sync + app state + mensagens ao vivo) é consultável:

GET/v1/sessions/:id/store/chats

Todos os chats conhecidos; ?jid= retorna um. store/contacts é o equivalente para contatos. Mudanças chegam como chats-upsert/update/delete, contacts-upsert/update e messaging-history-set no SSE.

POST/v1/sessions/:id/placeholder-resync

Pede ao celular o reenvio de mensagens placeholder: { "keys": [{ "remoteJid", "id" }] } → evento placeholder-resolved. history-sync/request pede histórico antigo de um chat ({ "chat", "oldestMsgId", "count?" }).

POST/v1/sessions/:id/media/update

Re-upload de mídia expirada (media retry): { "id", "chat", "mediaKey" } → novo directPath.

GET/v1/sessions/:id/props

Feature flags (AB props) do servidor do WhatsApp.

Telemetria WAM

Pipeline WAM opt-in com registry próprio embutido (444 eventos nomeados da WA Web — nada de pacote externo):

POST/v1/sessions/:id/wam/enable

Liga o pipeline da sessão.

POST/v1/sessions/:id/wam/commit

Commit nomeado ({ "name": "UiAction", "payload": { ... } }) ou cru ({ "eventId", "fields": [...] }).

POST/v1/sessions/:id/wam/flush

Envia o lote acumulado agora.

Mobile (companion-host)

Permite que uma conta primária (device 0) hospede o pareamento de companheiros. Rotas: mobile/parse-qr (parse local do QR do companheiro), mobile/pair-device, mobile/key-index-list, mobile/remove-companion e mobile/primary-hello. Os artefatos assinados são fornecidos pelo chamador.

Referência da API

Resumo de todas as rotas (:id = id da sessão). Detalhes de corpo/resposta nas seções acima e nos tipos do SDK.

ÁreaRotas
SessõesPOST/GET /v1/sessions, GET/DELETE /v1/sessions/:id, connect, disconnect, logout, passive, events (SSE)
Pareamentopair/qr, pair/code (POST/DELETE), pair/passkey/options|response|confirm
Mensagensmessages, messages/react|edit|revoke|read|poll-vote|pin|keep|limit-sharing|share-phone|request-phone|event-respond|poll-aggregate|comment, url-info, presence, presence/subscribe, chatstate
Mídiamedia/upload, media/download, media/delete, media/retry-receipt, media/update, stickers/fetch-pack
Businesslabels (POST), labels/edit|delete|chat|message, commerce/catalog|collections (GET), commerce/orders/:orderId (GET), commerce/products|products/update|products/delete|images (POST)
Store & miscstore/chats (GET, ?jid=), store/contacts, placeholder-resync, history-sync/request, props (GET), calls/link, GET /wa-web-version (raiz), GET /health (raiz)
Gruposgroups (POST/GET), groups/metadata|participants|join|leave|invite-info|invite-link|subject|description|settings|join-approval|member-add-mode|photo|requests|requests/update|link|subgroups|accept-invite-v4|revoke-invite-v4|member-label
Canaisnewsletters (POST), newsletters/info|subscribed|follow|messages|viewed|reaction|mute|update|updates|subscribers|admin-count|change-owner|demote|delete
Contaprofile/name|about|picture, privacy (GET/POST), blocklist (GET/POST), account/email (+verify), contact-qr, contact-qr/resolve, disappearing-timer, status, status/privacy
Usuáriosusers/check|info|profile-picture|business-profile|resolve-username|usernames
Chatschats/pin|archive|mute|read|delete|star|contact, appstate/fetch|mark-not-dirty
Extrasbots, bots/profile, bots/profiles, bots/prompt, broadcast-lists (+remove/send), calls/reject, wam/enable|commit|flush, mobile/*
SiteGET /, /docs, /privacidade, /termos (aliases /privacy e /terms) — servidos pela própria API

Eventos em tempo real (SSE)

GET/v1/sessions/:id/events

Stream text/event-stream com event: <nome> e data: <json>. No SDK: session.on('message', ...) com reconexão automática.

curl -N https://web.lagoshost.com.br/v1/sessions/$ID/events
# event: session
# data: {"id":"...","status":"connected"}
#
# event: message
# data: {"id":"3EB0...","chat":"5511@s.whatsapp.net","kind":"text","text":"oi!"}
EventoQuando
sessionMudança de estado da sessão (também é o primeiro frame do stream).
messageMensagem recebida (descriptografada/decodificada, incl. canais).
message-sent · message-ack · receiptEnvios próprios e recibos (entregue/lido/ouvido).
presence · chatstateOnline/offline e “digitando…”.
group · joined-group · pictureCiclo de vida de grupos e fotos.
call · call-offer · call-accept · call-reject · call-terminateSinalização de chamadas.
pushname · contact · pushname-settingNomes/contatos aprendidos ou alterados.
blocklist · privacy-settings · devicesSincronizações da conta vindas de outro dispositivo.
chats-upsert · chats-update · chats-delete · contacts-upsert · contacts-update · messaging-history-setStore sincronizada (estilo Baileys) a partir de history sync/app state/mensagens.
lid-mapping-update · settings-update · chat-lock · chats-lockMapeamento PN↔LID aprendido, settings do app state e trava de conversa.
group-member-tag-update · event-response · placeholder-resolvedMember label em grupos, RSVP de evento, placeholder recuperado do celular.
pin · archive · mute · star · mark-chat-as-read · clear-chat · delete-chatAções de chat (app state) feitas em qualquer dispositivo.
newsletter-live-update · media-retryPosts/contadores de canais e respostas de retry de mídia.
history-sync · appstate · appstate-keysSincronizações pós-pareamento e mutações brutas.
offline-sync-preview · offline-sync-completedBacklog offline ao conectar.
pairing-code* · pair-passkey-* · temporary-ban · identity-change · decrypt-error · message-retried · client-outdated · blocklist-change · user-about · mex-notification · newsletter-join/leave/mute-change/milestone · account-reachout-timelock · delete-for-me · label-edit/label-association-* · appstate-sync-complete/appstate-sync-error · rotate-adv-secretPareamento, ban temporário, troca de identidade Signal e erros de descriptografia.

Erros & códigos

Formato único: { "error": { "code": "CODIGO", "message": "..." } }.

HTTPSignificado
400VALIDATION_ERROR — corpo fora do schema (detalhes em error.details).
404Sessão inexistente.
409Sessão não pareada/conectada para a operação (ex.: SESSION_NOT_PAIRED).
502/503Falha falando com o WhatsApp (transporte/IQ) — tente de novo; veja os logs do servidor.
Dica: no SDK, capture WappMeowApiError e use error.code/error.status — os mesmos valores desta tabela.

Feito com o wappmeow — a API de WhatsApp que ronrona.