M
Cotação MutualDocumentação da operação · cotações por grupo com fees da Mutual · fase 1 (somente cotações)
🎛 Painel de controle
⚠️ Fase atual: o sistema apenas COTA. ORDERS_ENABLED=false — nenhuma ordem é criada (o código nem possui a chamada POST /crypto/orders). O toggle Compra (execução) nasce sempre desligado e o /COMPRAR encerra a cotação com registro para conclusão manual por um operador da Mutual.
1. Arquitetura
👥 Grupo WhatsAppcliente envia /COTAR, /COMPRAR
→MESSAGES_UPSERT
webhook
📱 Evolution API v2.3.7whatsapp.talkhub.me
instância talkbia
→POST /webhook
?token=…
🪙 cotacaomutualNode 22 · Express
Swarm + Traefik (talkhub)
→Bearer ak_ +
x-service-token
🏦 Mutual API v2merchants + fees (prod)
ticker (hml*)
↓ POST /message/sendText/talkbia (apikey) — resposta ao grupo
🎛 Painel /paineltoggles por canal/grupo · fila ao vivo
merchants/fees · registro · diagnóstico
↔PANEL_TOKEN
💾 Volume cotacaomutual_datasettings.json (toggles)
quote-log.jsonl (auditoria)
↔TTL 6h
🔗 Resolver de gruposconvite chat.whatsapp.com/…
→ JID via /group/inviteInfo
* A fonte do preço é o ticker (preço de mercado sem fee). Hoje ele é servido pela URL apis-hml (MUTUAL_CRYPTO_ENV=hml); a URL apis (prod) responde no formato "quote" com spread de provider embutido, usado apenas como último recurso. Com QUOTE_TICKER_FALLBACK=true, o sistema prefere o ticker onde quer que ele esteja — quando a Mutual publicar o ticker em prod, basta trocar MUTUAL_CRYPTO_ENV=prod.
2. Fluxo da cotação (/COTAR)
Mensagem chega pelo webhookEvolution envia messages.upsert. Filtros: só grupos (…@g.us), ignora fromMe (anti-loop) e outros eventos.
Comando interpretado/COTAR 25K USDT (qtd do ativo) · /COTAR 5000 BRL USDT (orçamento) · /COTAR 1 BTC BRL (venda) · /VENDER 1 BTC · aliases USD/dólar→USDC, K/M, vírgula decimal.
Merchant identificado pelo grupoBusca nos linkGroups dos merchants (cache 60s): JID direto, link de convite ou código — convites são resolvidos para JID na Evolution (/group/inviteInfo, cache 6h). Exige merchant.status=active e linkGroup.active=true.
Toggles do painelCotações desligadas no canal/grupo → responde que a cotação será conduzida manualmente (nunca cita "bot desligado").
Operação classificada + fee exataBRL→cripto=buy · BRL→USDC e USDC→BRL e cripto→cripto=conversion · cripto→BRL=sell. Fee localizada por operation+source+destination (cache 60s) — sem fee exata a cotação é interrompida (nunca usa fee de outro par).
FILA: 10 mensagens, requisições separadasCada mensagem traz os DOIS lados do par (compra e venda), cada um com a fee do seu lado — lado sem fee sai como sob consulta. Para cada mensagem: ticker NOVO na Mutual (nunca cacheado, preços mudam a cada segundo) → fee aplicada na direção correta → sendText no grupo (📊 Cotação X/10) → registro completo no painel. Intervalo padrão 3s. Nova /COTAR substitui a fila; /COMPRAR interrompe.
Seleção do preço no ticker (lado do cliente)
| Direção | Lado do book | Campo do ticker |
| Cliente COMPRA o ativo (BRL → cripto) | ask | sell (fallback last, buy) |
| Cliente VENDE o ativo (cripto → BRL) | bid | buy (fallback last, sell) |
| Cripto → cripto (taxa cruzada via BRL) | vende origem no bid ÷ compra destino no ask | 2 requisições por mensagem |
3. Fluxo da compra (/COMPRAR)
/COMPRA ou /VENDA chega (com ou sem argumentos)/COMPRA · /VENDA USDT · /COMPRA 50K USDT. Aliases: /ORDER, /ORDEM, /FECHAR.
Argumentos conferidos contra a cotação ativaAtivo e quantidade precisam bater com a cotação do grupo (fila ativa ou concluída há < 15 min). Divergência (ex: cotação de USDT e pedido de BTC) → NÃO confirma e orienta: "envie /COTAR <valor> <ativo>".
Cotação é TRAVADA (consumida)Cada cotação confirma no máximo UMA operação. O próximo /COMPRAR exige uma /COTAR nova — preços mudam a cada segundo.
Registro da operação emitido no grupoID do grupo · ID da Transação (UUID) · Data (America/Sao_Paulo) · Tipo (COMPRA/VENDA/CONVERSÃO + ativo) · Cotação final · Montantes Total/Pendente no ativo e em BRL.
Conclusão manual"A operação será concluída manualmente por um operador da Mutual." — nenhuma ordem é criada nesta fase. Tudo fica no Registro de operações do painel (transactionId, fees, ticker cru) para o operador dar sequência.
Exemplo de registro emitido
✅ Pedido recebido!
ID do grupo:
120363406233321709@g.us
ID da Transação:
b571d632-add7-43f8-9750-1e0b75d2f2f1
📅 Data da Operação:
11/07/2026 18:07
Tipo de Operação:
COMPRA USDT
Cotação:
1 USDT = R$ 5,16362
💵 Montante em USDT:
Total: 100.000 USDT
Pendente: 100.000 USDT
💼 Montante em BRL:
Total: R$ 516.361,63
Pendente: R$ 516.361,63
A operação será concluída manualmente por um
operador da Mutual. Aguarde a confirmação
aqui no grupo. 🤝
Casos de recusa
| Situação | Resposta |
| Sem cotação válida (nenhuma, expirada >15min ou já consumida) | "É preciso uma cotação atualizada… envie /COTAR e confirme com /COMPRAR" |
| Argumentos divergentes da cotação ativa | "A cotação ativa é: … Para operar X, envie /COTAR X" |
| Argumentos incompreensíveis | "Não entendi os detalhes… /COMPRAR confirma a cotação ativa" |
| Grupo sem merchant ativo vinculado | "Este grupo ainda não está vinculado a um cliente habilitado para cotações." |
| Compra LIGADA no painel (+ ORDERS_ENABLED=false) | Mesmo registro + "execução automática ainda não habilitada… conclusão manual" |
4. cURLs — todas as chamadas externas
4.1 Mutual · Merchants (produção, cache 60s)
curl --location -G 'https://apis.mutual.app.br/api/v2/resource/merchants' \
--header 'Authorization: Bearer ak_SUA_CHAVE' \
--header 'Content-Type: application/json' \
--header 'x-service-token: SEU_SERVICE_TOKEN' \
--data-urlencode 'page=1' --data-urlencode 'limit=100'
# usa: id, legalName, status, linkGroups[{channel, groupId, name, active}]
# groupId aceita: JID (…@g.us), link https://chat.whatsapp.com/CODIGO ou código puro
4.2 Mutual · Fees do merchant (produção, cache 60s)
curl --location -G 'https://apis.mutual.app.br/api/v2/resource/fees/merchant/org_3DXaWmcAaguU8rn2ryheTS2kRdw' \
--header 'Authorization: Bearer ak_SUA_CHAVE' \
--header 'Content-Type: application/json' \
--header 'x-service-token: SEU_SERVICE_TOKEN'
# usa: operation + sourceAsset + destinationAsset + feeFixed + feePercentage (match EXATO)
4.3 Mutual · Cotação-base / ticker fonte do preço (NUNCA cacheada — 1 requisição por mensagem da fila)
curl --location -G 'https://apis-hml.mutual.app.br/api/v2/crypto/quote' \
--header 'Authorization: Bearer ak_SUA_CHAVE' \
--header 'Content-Type: application/json' \
--header 'x-service-token: SEU_SERVICE_TOKEN' \
--data-urlencode 'symbol=USDT-BRL' \
--data-urlencode 'amount=1000' \
--data-urlencode 'sourceAsset=BRL' \
--data-urlencode 'targetAsset=USDT' \
--data-urlencode 'targetNetwork=TRON'
# Resposta formato TICKER (preço de mercado SEM fee — é a base da cotação):
{"error":false,"message":"Quote fetched successfully","data":{
"buy":"5.1635","sell":"5.1636","last":"5.1636","high":"5.1659","low":"5.1375",
"open":"5.1453","vol":"7768643.22971","pair":"USDT-BRL","date":1783801754080}}
4.4 Mutual · Mesmo endpoint em produção formato "quote" — só último recurso
# Mesma chamada em https://apis.mutual.app.br → responde com spread de provider embutido:
{"error":false,"data":{"quote_id":"…","type":"BUY","source_asset":"BRL","target_asset":"USDT",
"source_amount":"1000000","target_amount":"184501.845018","price":"5.42","fee_bps":50,
"provider":"MERCADO_BITCOIN","locked_at":"…","expires_at":"…"}}
# 5,42 ≠ mercado (5,1636) → NÃO serve de base. Com QUOTE_TICKER_FALLBACK=true o sistema
# detecta o formato e busca o ticker na URL alternativa automaticamente.
4.5 Evolution · Envio da mensagem ao grupo
curl -X POST 'https://whatsapp.talkhub.me/message/sendText/talkbia' \
--header 'apikey: SUA_EVOLUTION_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"number":"120363429012757266@g.us","text":"📊 Cotação BRL → USDT (1/10)…"}'
4.6 Evolution · Resolver convite → JID (cache 6h)
curl -G 'https://whatsapp.talkhub.me/group/inviteInfo/talkbia' \
--header 'apikey: SUA_EVOLUTION_API_KEY' \
--data-urlencode 'inviteCode=C34dh5vXFPJ8wgOGlE9LYG'
# resposta: { "id": "120363429012757266@g.us", "subject": "teste", … }
4.7 Evolution · Registro do webhook (feito pelo setup.sh)
curl -X POST 'https://whatsapp.talkhub.me/webhook/set/talkbia' \
--header 'apikey: SUA_EVOLUTION_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"webhook":{"enabled":true,
"url":"https://cotacaomutual.talkhub.me/webhook?token=SEU_WEBHOOK_TOKEN",
"webhookByEvents":false,"webhookBase64":false,"events":["MESSAGES_UPSERT"]}}'
4.8 Nosso serviço · Webhook de entrada (contrato genérico p/ testes)
curl -X POST 'https://cotacaomutual.talkhub.me/webhook?token=SEU_WEBHOOK_TOKEN' \
--header 'Content-Type: application/json' \
--data '{"channel":"whatsapp","groupId":"120363429012757266@g.us","text":"/COTAR 25K USDT"}'
4.9 Nosso serviço · API do painel
# visão geral (canais, grupos, toggles, filas)
curl 'https://cotacaomutual.talkhub.me/api/panel/overview' -H 'Authorization: Bearer SEU_PANEL_TOKEN'
# liga/desliga bots (compra nasce SEMPRE desligada; ativação manual)
curl -X POST 'https://cotacaomutual.talkhub.me/api/panel/toggle' \
-H 'Authorization: Bearer SEU_PANEL_TOKEN' -H 'Content-Type: application/json' \
-d '{"scope":"group","channel":"whatsapp","groupId":"1203…@g.us","feature":"buy","enabled":true}'
# registro detalhado + diagnóstico da cotação-base ao vivo
curl 'https://cotacaomutual.talkhub.me/api/panel/logs?limit=100' -H 'Authorization: Bearer SEU_PANEL_TOKEN'
curl 'https://cotacaomutual.talkhub.me/api/panel/diag/quote?asset=USDT&side=buy&env=hml' -H 'Authorization: Bearer SEU_PANEL_TOKEN'
5. Variáveis utilizadas na cotação
5.1 Parâmetros da requisição de ticker
| Parâmetro | Valor | Origem |
symbol | {ATIVO}-BRL (ex: USDT-BRL, BTC-BRL) | ativo da perna cotada |
amount | orçamento em BRL (comando … BRL …) ou QUOTE_REFERENCE_BRL_AMOUNT (1000) quando o usuário informa quantidade do ativo | comando do grupo |
sourceAsset | BRL | fixo (referência) |
targetAsset | BTC · ETH · USDT · USDC | comando (após normalização de aliases) |
targetNetwork | BTC→BITCOIN · ETH→ERC20 · USDT→TRON · USDC→ERC20 | src/constants.ts |
5.2 Variáveis de ambiente (.env)
| Variável | Valor atual | Papel na cotação |
MUTUAL_API_KEY / MUTUAL_SERVICE_TOKEN | segredo | autenticação em TODAS as chamadas à Mutual |
MUTUAL_PROD_BASE_URL | https://apis.mutual.app.br | merchants + fees (sempre) · quote se env=prod |
MUTUAL_HML_BASE_URL | https://apis-hml.mutual.app.br | ticker (fonte atual do preço) |
MUTUAL_CRYPTO_ENV | hml (interino) | ambiente consultado primeiro no /crypto/quote |
QUOTE_TICKER_FALLBACK | true | se vier formato "quote" (spread), busca o ticker na URL alternativa |
QUOTE_QUEUE_MESSAGES | 10 | mensagens por fila de cotação |
QUOTE_QUEUE_INTERVAL_MS | 3000 | intervalo entre mensagens (preço novo a cada uma) |
QUOTE_REFERENCE_BRL_AMOUNT | 1000 | amount de referência p/ descobrir preço unitário |
MERCHANT_CACHE_TTL_MS / FEE_CACHE_TTL_MS | 60000 | cache de merchants/fees (ticker NUNCA cacheia) |
ORDERS_ENABLED | false | trava absoluta: nenhuma ordem nesta fase |
OUTBOUND_MODE + EVOLUTION_BASE_URL/INSTANCE/API_KEY | evolution · talkbia | entrega das mensagens ao grupo |
WEBHOOK_TOKEN / PANEL_TOKEN | gerados no setup.sh | proteção do webhook e do painel |
DATA_DIR | /app/data (volume) | toggles do painel + quote-log.jsonl |
5.3 Variáveis do cálculo (por mensagem da fila)
| Variável | De onde vem |
baseUnitPrice | ticker: sell(ask) na compra · buy(bid) na venda · fallback last → razão source_amount/target_amount → price |
feePercentage · feeFixed | fee EXATA do merchant (operation+source+destination) — AMBAS em pontos percentuais, somadas: taxa = (fixa + percentual)/100 |
quantity / amountBRL | comando do grupo (25K → 25.000; aceita K/M e vírgula) |
finalUnitPrice · finalTotal / netAmount | resultado das fórmulas abaixo — único dado exibido no grupo |
taxa = (feeFixed + feePercentage) / 100
← as duas fees da Mutual são PONTOS PERCENTUAIS que se SOMAM
(ex: fixa 0.1 + percentual 0.65 → taxa total de 0,75%)
① Quantidade do ativo (/COTAR 25K USDT — cliente paga mais):
finalTotal = quantidade × baseUnitPrice × (1 + taxa)
② Orçamento em BRL (/COTAR 5000 BRL USDT — fee sai do orçamento):
baseAvailable = amountBRL / (1 + taxa)
quantity = baseAvailable / baseUnitPrice
③ Venda/conversão da origem (/COTAR 1 BTC BRL — cliente recebe menos):
net = quantidade × baseUnitPrice × (1 − taxa)
Exemplo real (fee do merchant: fixa 0% + percentual 0,65% = 0,65%)
| Passo | Valor |
| Comando no grupo | /COTAR 1K USDT |
| Ticker (ask, sem fee) | 1 USDT = R$ 5,0781 |
| baseTotal = 1.000 × 5,0781 | R$ 5.078,10 |
| taxa total = (0 + 0,65) / 100 | 0,65% |
| fee = 5.078,10 × 0,0065 | R$ 33,01 |
| finalTotal (mostrado no grupo) | R$ 5.111,11 → 1 USDT = R$ 5,11111 |
O grupo vê apenas a cotação final. feePercentage, feeFixed, preço-base sem fee, merchantId e ticker cru ficam SÓ no painel/registro (data/quote-log.jsonl).
7. Requisitos técnicos
Infraestrutura
| VPS | Docker Swarm ativo (nó manager) |
| Rede | overlay externa talkhub |
| Proxy | Traefik v3 · entrypoint websecure · resolver letsencryptresolver |
| DNS | cotacaomutual.talkhub.me → IP da VPS |
| Imagem | cotacaomutual:latest (build local, Node 22-alpine, multi-stage) |
| Volume | cotacaomutual_data (externo) → /app/data |
| Recursos | 0.5 CPU · 512 MB · 1 réplica |
| Healthcheck | GET /health a cada 30s |
| Portas | nenhuma publicada no host (Traefik → :3000 interno) |
Integrações e credenciais
| Mutual API v2 | chave ak_… + x-service-token válidos p/ prod e hml |
| Evolution API | v2.3.7 · instância talkbia conectada · apikey |
| Webhook | MESSAGES_UPSERT → /webhook?token=… (registrado pelo setup.sh) |
| Cadastro do grupo | merchant ativo + linkGroup ativo (JID, link ou código de convite) |
| Fees do merchant | matriz por par cadastrada na Mutual (sem fee = sem cotação) |
| Fuso | TZ=America/Sao_Paulo (datas do registro) |
| Node/dev | Node ≥ 20 · npm run build · npm test (66 testes) |
8. Resolução de merchants (resiliência)
A listagem GET /resource/merchants pode recusar o service token (Service token not accepted on this endpoint) e a API não expõe merchant por ID — /resource/merchants/{id} responde 404 em HTML (Cannot GET …). O sistema tem quatro níveis; o primeiro que funcionar vence, e o painel mostra qual está em uso.
ListagemGET /api/v2/resource/merchants?page&limit — caminho normal, traz linkGroups completos. Alimenta o snapshot em disco.
Consulta individual por organização (fila)Para cada ID conhecido tenta o endpoint direto e, como ele não existe, cai na sonda via fees: GET /resource/fees/merchant/{orgId} — 200 confirma que o merchant existe e está acessível; 404 significa fora desta conta. O nome vem do catálogo semente/snapshot. IDs que não respondem ficam 10 min em quarentena (sem remartelar a API) e o 404 esperado não vai para o log.
Snapshot em discodata/merchants-snapshot.json — último estado válido. Os linkGroups do snapshot são preservados quando a resposta individual não os traz: o vínculo de grupo nunca se perde.
Vínculos manuais do painelGrupo → merchant informado pelo link de convite, aplicado por cima de todos os níveis. Se nenhuma fonte da Mutual responder, os grupos vinculados continuam cotando normalmente.
8.1 Origem dos IDs de organização
| Fonte | Onde |
| Catálogo semente | src/mutual/known-merchants.ts — IDs + razão social já observados em produção |
| Ambiente | MUTUAL_KNOWN_MERCHANT_IDS (separados por vírgula) |
| Snapshot | todo merchant já visto fica registrado automaticamente |
| Vínculos | todo merchant vinculado no painel entra na lista de IDs conhecidos |
8.2 Vincular grupo → merchant pelo link de convite
O usuário final não sabe o JID interno do grupo — ele tem o link de convite. É esse link que se cola no painel; a resolução para JID acontece no clique.
Operador cola o linkhttps://chat.whatsapp.com/C34dh5vXFPJ8wgOGlE9LYG — o JID interno (…@g.us) também é aceito, para quem já o tem. Qualquer outro texto é recusado com instrução clara.
Resolução na horaGET /group/inviteInfo na Evolution, com consulta forçada (o cache guarda falhas por 60s; sem forçar, um novo clique falharia sozinho depois de a instância entrar no grupo).
Grava pelo JIDO JID resolvido é a forma canônica do vínculo; o link fica guardado como referência e aparece na linha do grupo (convite: …), com a etiqueta manual.
Convite não resolveu? A instância precisa estar dentro do grupo (ou o link foi revogado). O painel avisa e grava o vínculo pelo próprio link — ele passa a valer assim que a resolução funcionar, porque o GroupMatcher também resolve convites na chegada da mensagem. Coloque a instância no grupo e clique em Vincular de novo: o registro feito pelo link é substituído pelo JID, sem duplicar a linha do grupo.
Cada linha de grupo tem um botão vincular que já preenche o formulário. Para remover, deixe o merchant vazio e clique em Desvincular — aceita o link ou o JID, independentemente de qual foi usado no cadastro. O vínculo é gravado em DATA_DIR (volume externo) e sobrevive a reinícios e redeploys.
curl -X POST 'https://cotacaomutual.talkhub.me/api/panel/bind-group' \
-H 'Authorization: Bearer SEU_PANEL_TOKEN' -H 'Content-Type: application/json' \
-d '{"channel":"whatsapp","groupId":"https://chat.whatsapp.com/C34dh5vXFPJ8wgOGlE9LYG",
"merchantId":"org_3G8y…","label":"Grupo VIZZO"}'
# resposta quando o convite resolve:
{"ok":true,"binding":{"channel":"whatsapp","groupId":"120363429012757266@g.us",
"merchantId":"org_3G8y…","invite":"https://chat.whatsapp.com/C34dh…"},
"resolvedJid":"120363429012757266@g.us","warning":null}
# warning != null → convite não resolveu agora; o vínculo ficou salvo pelo link
# desvincular: merchantId vazio (link ou JID)
curl -X POST 'https://cotacaomutual.talkhub.me/api/panel/bind-group' \
-H 'Authorization: Bearer SEU_PANEL_TOKEN' -H 'Content-Type: application/json' \
-d '{"channel":"whatsapp","groupId":"https://chat.whatsapp.com/C34dh5vXFPJ8wgOGlE9LYG","merchantId":""}'
# catálogo para o seletor + vínculos ativos
curl 'https://cotacaomutual.talkhub.me/api/panel/known-merchants' -H 'Authorization: Bearer SEU_PANEL_TOKEN'
# diagnóstico: testa listagem E consulta individual ao vivo, com status e corpo do erro
curl 'https://cotacaomutual.talkhub.me/api/panel/diag/merchants' -H 'Authorization: Bearer SEU_PANEL_TOKEN'
9. Regras da fase atual
- Só cotações.
ORDERS_ENABLED=false; a chamada de criação de ordem não existe no código.
- Compra (execução) sempre nasce desligada — ativação exclusivamente manual no painel, por canal ou grupo.
- Fila de 10 cotações por comando, cada mensagem com requisição própria (sem cache de ticker), interrompível pelo /COMPRAR.
- 1 cotação = 1 confirmação. O /COMPRAR trava (consome) a cotação; nova confirmação exige /COTAR novo. Validade da cotação para confirmação: 15 min.
- Argumentos do /COMPRAR são conferidos (ativo e quantidade) contra a cotação ativa — divergência não confirma.
- Fee exata obrigatória por operation+source+destination — sem fallback silencioso.
- Nunca citar estado do bot — recursos inativos respondem apenas que a operação será concluída manualmente por um operador da Mutual.
- Grupo vê só a cotação final; todos os internos vão para o painel e o quote-log (auditoria completa por transactionId).
10. Operação (runbook)
# Primeiro deploy (pergunta só a MUTUAL_API_KEY; gera tokens; registra webhook)
cd /root/cotacaomutual.talkhub.me && bash setup.sh
# Atualização rápida
git pull && docker build -t cotacaomutual:latest . && \
docker service update --image cotacaomutual:latest --force cotacaomutual_cotacaomutual
# Redeploy limpo (remove stack, limpa imagens do serviço, rebuild --no-cache, preserva .env e volume)
bash redeploy.sh # flags: --no-pull · --wipe-data · --yes
# Observabilidade
docker service logs -f cotacaomutual_cotacaomutual
https://cotacaomutual.talkhub.me/painel # toggles, fila ao vivo, merchants/fees, registro, diagnóstico
https://cotacaomutual.talkhub.me/health
| Sintoma | Causa provável / ação |
| "grupo não vinculado" | linkGroup ausente/inativo na Mutual, merchant inativo, ou convite não resolvível (registre o JID direto). Ver logs: Falha ao resolver convite. Solução imediata: colar o link de convite do grupo no painel e vincular ao merchant (§8.2). |
| Painel sem merchants / lista vazia | Painel → Testar merchants (/api/panel/diag/merchants) mostra o erro exato de cada caminho. Com a listagem recusando o token, o rótulo de fonte deve indicar consulta individual; se nenhum caminho responder, use os vínculos manuais (§8.2) — os grupos vinculados continuam cotando. |
| "não há taxa configurada" | Falta a fee EXATA do par no merchant — painel → Merchants & Fees mostra os pendentes. |
| "não foi possível gerar a cotação" | Painel → Diagnóstico (mostra o erro HTTP exato da Mutual) e Registro de operações. |
| Preço divergente do mercado | Conferir priceSource no diagnóstico: deve ser ticker/ticker-fallback, nunca quote. |
| Bot não responde no grupo | Webhook na Evolution (URL/token), instância conectada, docker service logs. |