# wapp-api — mensagens recebidas O que chega no seu número. É o par da rota de envio, e é com isto que se monta atendimento automático do outro lado. Duas formas, e o recomendado é usar as duas: o **callback** avisa na hora, a **rota de leitura** recupera o que se perdeu enquanto seu sistema esteve fora do ar. ## `GET /api/mensagens/entrada` | Parâmetro | Observação | |---|---| | `depois_de` | id a partir do qual buscar (exclusivo). É o cursor | | `instancia` | filtra por instância | | `numero` | filtra por remetente | | `limite` | padrão 100, teto 500 | | `incluir_minhas` | `1` = também o que saiu pelo celular (`de_mim`) | | `incluir_grupos` | `1` = também mensagens de grupo (sem ele, grupo nunca vem) | **200** → `{"total":2,"limite":100,"ultimo_id":8,"mensagens":[...]}` Cada item traz `id`, `numero_origem`, `nome_contato`, `tipo`, `conteudo`, `id_mensagem_wa`, `recebido_em`, `instancia` e, quando há anexo, `nome_arquivo`, `mimetype`, `tamanho_bytes` e `midia_status`. Com `incluir_minhas=1` vêm também as linhas `de_mim` (digitadas no celular), com `status_entrega`: `enviado`, `entregue`, `lido` ou nulo (sem notícia ainda). Com `incluir_grupos=1`, a linha de grupo vem com `e_grupo = 1`, `numero_origem` = JID do grupo (`120363...@g.us`), `grupo_nome` (cache de grupos; nulo até o cache conhecer), `participante` (LID ou telefone de quem falou), `participante_telefone` (quando o LID já foi mapeado) e o nome de quem falou em `nome_contato`. Responder no grupo: `POST /api/mensagens` com o JID — cliente com **Grupos livres** (`docs/api-grupos.md`). Ordem **crescente** de id — atendimento lê na ordem em que a pessoa falou, ao contrário da listagem de saída, que é histórico. Guarde o `ultimo_id` e mande em `depois_de` na chamada seguinte: não reprocessa nem pula. Lote vazio devolve `ultimo_id` nulo; mantenha o cursor antigo. ## `GET /api/mensagens/entrada/status?ids=12,15` Linhas `de_mim`: entrega/leitura do destinatário — você as leu pelo cursor antes de serem entregues, e o ✓✓ chega depois. Linhas recebidas: `lido` quando alguém leu no celular do número (nulo = ainda não), para tirar a conversa de "não lidas" no seu sistema. Até 100 ids; conta no limite por minuto, então pergunte só pelas recentes que ainda não estão `lido`. **200** → `{"status": {"12": "entregue", "15": "lido"}}` — id que não é seu fica de fora. **400** → lista vazia, inválida ou > 100. ## `POST /api/mensagens/entrada/lidas` O caminho de volta: seu sistema leu, o WhatsApp marca como lida — quem mandou vê o ✓✓ azul e o celular do número tira a conversa de "não lidas". Corpo `{"ids": [12, 15]}` (até 100). Só linhas recebidas, suas e ainda sem leitura; o resto é ignorado. Usa a chave original do webhook (`@lid`, `participant` no grupo). **200** → `{"marcadas": 2}` (e elas passam a `status_entrega = lido`). **400** → lista vazia, inválida ou > 100. **502** → a Evolution recusou; nada foi gravado, pode tentar de novo. ## `GET /api/mensagens/entrada//arquivo` Devolve o anexo com o `Content-Type` e o nome originais. | Código | Quando | |---|---| | **200** | o arquivo | | **404** | id não é seu, ou a mensagem não tem anexo (`midia_status` diz qual) | | **500** | o banco diz que existe e o disco não — falha nossa, registrada aqui | Existe só enquanto a retenção guardar (padrão 90 dias). Se você precisa do arquivo depois disso, copie para o seu lado quando o aviso chegar. ## Callback `mensagem_recebida` Ativado sob pedido. Assinatura, retentativa e o resto do contrato estão em `docs/api-callbacks.md` — aqui fica só o formato. ```json { "evento": "mensagem_recebida", "id": 2, "instancia": "principal", "numero": "5547999998888", "nome": "Rosana", "tipo": "conversation", "texto": "qual o horário de funcionamento?", "arquivo": null, "id_mensagem_wa": "AC93281C275089E2A229307DB0CA70D4", "em": "2026-08-28T17:46:56" } ``` | Campo | O que é | |---|---| | `id` | id da mensagem recebida; serve de cursor na rota de leitura | | `numero` | quem mandou, E.164 sem `+` | | `nome` | nome de perfil do WhatsApp, pode vir `null` | | `tipo` | `conversation`, `imageMessage`, `audioMessage`, `documentMessage`, `stickerMessage`, `reactionMessage`, ... | | `texto` | texto digitado, transcrição local ou legenda de imagem/vídeo/documento. `null` no resto | | `arquivo` | dados do anexo, ou `null` | | `em` | quando chegou aqui | Com anexo: ```json "arquivo": { "nome": "foto.jpeg", "mimetype": "image/jpeg", "tamanho_bytes": 228729, "url": "/api/mensagens/entrada/1/arquivo" } ``` ## Anexos Imagem, vídeo, áudio e documento são baixados por nós **antes** de avisarmos. Quando o aviso chega, o arquivo já está aqui — você não precisa ficar tentando. Baixe em `arquivo.url`, autenticando com sua chave. Exceção: áudio recebido em conversa direta, com transcrição habilitada, é temporário e descartado. Seu callback tem `arquivo: null` e segue o contrato de texto descrito abaixo; não há arquivo para baixar — a não ser que o cliente tenha **"Guardar o arquivo do áudio transcrito"** ligado (painel, tela do cliente): aí o áudio vem como anexo comum (`nome_arquivo`, `mimetype`, rota `/arquivo`), junto com o texto, inclusive se a transcrição falhar. `arquivo` nulo junto com um `tipo` de mídia quer dizer que o download não deu certo depois de 3 tentativas. **Não é mensagem sem anexo, é anexo que não veio.** Distinga pelo `tipo`, não pelo `arquivo`. Figurinha também é baixada, como imagem `webp`. ## Áudio como entrada de texto Com transcrição local habilitada no gateway, `audioMessage`/`pttMessage` recebidos de terceiros em conversa direta chegam como `tipo: conversation`, com `texto` preenchido, `transcrito: true` e `tipo_original: audioMessage` (ou `pttMessage`). Mesmo evento `mensagem_recebida`, id e assinatura HMAC. O consumidor encaminha `texto` ao mesmo interpretador das mensagens digitadas. Não é preciso implementar reconhecimento de voz no aplicativo do robô. Na rota de leitura, a transcrição está em `conteudo` e o `tipo` também é adaptado para `conversation`. No banco/painel o tipo original é preservado. `midia_status`: `audio_pendente`, `transcrita` ou `audio_falhou`. Enquanto há áudio pendente, a rota retorna só as linhas anteriores a ele nos filtros consultados, sem avançar o cursor sobre uma transcrição incompleta. Em falha, o tipo continua sendo áudio e o texto fica nulo; não interpretar isso como comando. Onde há callback de entrada ativo, o gateway enfileira uma orientação para reenviar ou digitar, respeitando opt-out e limites. O aplicativo não precisa mandar uma segunda orientação para essa falha. Callbacks posteriores da mesma conversa esperam a transcrição e a entrega do callback do áudio (ou sua desistência). As demais conversas continuam. Retentativas ainda exigem idempotência no consumidor, como antes. ## O que não chega - **mensagem de grupo** — é registrada, mas fica só no painel administrativo. Não sai por esta rota nem por callback: o campo `numero` é sempre telefone, e um JID de grupo ali quebraria quem já consome; - **o eco da sua própria mensagem** — o que você manda não volta como recebido. ## Cuidados para atendimento automático **Filtre pelo `tipo`.** Figurinha e reação viram aviso. Responder a um "joinha" é o erro mais fácil de cometer aqui. **Trave o laço.** Se do outro lado houver outro robô, os dois se respondem indefinidamente. Ponha um teto de respostas automáticas por número por hora. **Receber é 24 h, responder não.** O aviso chega a qualquer hora, mas o envio respeita janela de horário e teto diário — ver `docs/api.md`.