O que aconteceu?
O job GET /api/v1/cron/contact-avatars marca contatos brasileiros como no_picture mesmo quando o WAHA tem a foto disponível. O avatar nunca aparece no Inbox para esses contatos.
A causa é o chatId que o job monta. Ele usa uma função local que deriva o identificador apenas do telefone gravado:
// app/api/v1/cron/contact-avatars/route.ts:50-54
function chatIdFromIdentity(identity: string): string | null {
if (identity.startsWith("lid:")) return `${identity.slice(4)}@lid`;
if (identity.startsWith("phone:")) return `${identity.slice(6).replace(/\D/g, "")}@c.us`;
return null;
}
E a consulta nem seleciona a coluna wa_lid:
// route.ts:78-79
.select("id, organization_id, wa_identity, avatar_storage_path")
.not("wa_identity", "is", null)
Como wa_identity é gerada a partir do telefone, um contato cujo wa_id real não tem o nono dígito produz um chatId que não existe no WhatsApp. O WAHA responde profilePictureURL: null, e o job classifica isso como "contato sem foto" — o ramo que o próprio código comenta como "estado normal, não erro". O defeito fica invisível: failed: 0.
Por que parece inconsistente com o resto do projeto
O repositório já resolve esse problema em três lugares, e nenhum deles é usado por este cron:
-
resolveWahaChatId (lib/waha/send.ts:54-62) — a regra canônica. Tenta waLid antes do telefone:
if (input.waLid) return `${input.waLid}@lid`;
if (input.waIdentity?.startsWith("lid:")) return `${input.waIdentity.slice(4)}@lid`;
if (input.phoneNumber) return `${input.phoneNumber.replace(/\D/g, "")}@c.us`;
-
chatIdOf (lib/agent-engine/edge/crm/session-reconciler.ts:174-180) — mesma ordem, com o comentário que nomeia a causa:
wa_lid na frente pelo motivo da 0122: wa_identity é GERADA com o telefone antes do lid
-
resolve-contact-whatsapp-id.ts — descreve o sintoma de forma idêntica ao deste relato:
No WhatsApp BR o CRM grava +5531998966398 (13 dígitos) mas o wa_id registrado pode ser 553198966398 (12, sem o nono). […] WAHA documenta GET /api/contacts/check-exists para isso; tentamos todas as variantes de busca (phoneLookupVariants) antes de cair no número bruto.
O cron de avatares parece ter ficado de fora dessas correções.
Comportamento esperado
O job deveria resolver o chatId pela mesma regra do resto da base: wa_lid primeiro, e — quando só houver telefone — passar pelas variantes de phoneLookupVariants / check-exists antes de desistir.
Passos para reproduzir
- Ter um contato brasileiro cujo
wa_id no WhatsApp não tenha o nono dígito (número antigo). O CRM grava phone_number = +55AA9BBBBCCCC (13 dígitos), mas o WhatsApp registra 55AABBBBCCCC (12).
- Receber uma mensagem desse contato — a conversa é criada normalmente e as mensagens funcionam (o
chatId vem pronto no webhook).
- Aguardar (ou disparar)
GET /api/v1/cron/contact-avatars.
- Observar a resposta:
{"scanned":1,"updated":0,"no_picture":1,"failed":0}.
- Consultar o banco:
avatar_updated_at é preenchido, avatar_storage_path continua NULL.
- Consultar o WAHA diretamente com os três identificadores — a foto existe, só não pelo
chatId que o cron monta.
Medição na instalação
Mesmo contato, mesma sessão, três identificadores (números mascarados):
contactId enviado ao WAHA |
Resposta |
55AA9BBBBCCCC@c.us — o que o cron monta (13 díg.) |
{"profilePictureURL": null} |
55AABBBBCCCC@c.us — o wa_id real (12 díg.) |
{"profilePictureURL": "https://pps.whatsapp.net/…"} |
<wa_lid>@lid — o LID, já presente em contacts.wa_lid |
{"profilePictureURL": "https://pps.whatsapp.net/…"} |
O próprio WAHA aponta o identificador correto:
GET /api/contacts/check-exists?phone=55AA9BBBBCCCC&session=<sessao>
→ {"numberExists": true, "chatId": "55AABBBBCCCC@c.us"}
Repetir com refresh=true não muda o resultado. A linha do contato já tem o wa_lid preenchido, e ele funciona — o job apenas não o lê.
Ambiente
DeskcommCRM 1.19.0 (imagem ghcr.io/melgarafael/deskcommcrm:stable)
commit e142504
Instalação self-host via hostgator-setup-kit/install.sh --yes, em VPS Hostinger
SO Ubuntu 24.04.4 LTS · Docker 29.7.2 · Compose v5.5.0
Proxy Caddy (o do kit), HTTPS Let's Encrypt
Banco Supabase cloud (Postgres 17.6, região sa-east-1, pooler session mode)
WAHA devlikeapro/waha:latest-2026.7.2 — engine NOWEB, tier CORE
Saída de /api/v1/health:
{"data":{"status":"healthy","version":"1.19.0",
"checks":{"supabase":{"status":"ok","latency_ms":78},
"redis":{"status":"ok","latency_ms":13},
"waha":{"status":"ok","latency_ms":10}}}}
Logs / observações
Resposta do job, depois de limpar avatar_updated_at para forçar o reprocessamento:
$ curl -H "Authorization: Bearer $INTERNAL_CRON_SECRET" \
http://127.0.0.1:3000/api/v1/cron/contact-avatars
{"data":{"scanned":1,"updated":0,"no_picture":1,"failed":0}}
Estado da linha em contacts (números mascarados):
nome | phone_number | wa_identity | wa_lid | avatar_storage_path
-------+-----------------+----------------------+-----------------+---------------------
**** | +55AA9BBBBCCCC | phone:+55AA9BBBBCCCC | <lid presente> | (NULL)
Sem stack trace: o caminho percorrido é o de "sem foto", não o de erro — por isso failed: 0 e nada nos logs do app. É justamente o que torna o defeito silencioso em produção.
Sugestão de correção
Trocar a chatIdFromIdentity local por resolveWahaChatId (lib/waha/send.ts), incluindo wa_lid no .select() da query. Isso alinha o cron às outras duas implementações e resolve o caso comum sem chamada extra ao WAHA. Para contatos que só tenham telefone, o caminho de resolve-contact-whatsapp-id.ts (check-exists + phoneLookupVariants) cobriria o restante.
Se ajudar, abro um PR.
O que aconteceu?
O job
GET /api/v1/cron/contact-avatarsmarca contatos brasileiros comono_picturemesmo quando o WAHA tem a foto disponível. O avatar nunca aparece no Inbox para esses contatos.A causa é o
chatIdque o job monta. Ele usa uma função local que deriva o identificador apenas do telefone gravado:E a consulta nem seleciona a coluna
wa_lid:Como
wa_identityé gerada a partir do telefone, um contato cujowa_idreal não tem o nono dígito produz umchatIdque não existe no WhatsApp. O WAHA respondeprofilePictureURL: null, e o job classifica isso como "contato sem foto" — o ramo que o próprio código comenta como "estado normal, não erro". O defeito fica invisível:failed: 0.Por que parece inconsistente com o resto do projeto
O repositório já resolve esse problema em três lugares, e nenhum deles é usado por este cron:
resolveWahaChatId(lib/waha/send.ts:54-62) — a regra canônica. TentawaLidantes do telefone:chatIdOf(lib/agent-engine/edge/crm/session-reconciler.ts:174-180) — mesma ordem, com o comentário que nomeia a causa:resolve-contact-whatsapp-id.ts— descreve o sintoma de forma idêntica ao deste relato:O cron de avatares parece ter ficado de fora dessas correções.
Comportamento esperado
O job deveria resolver o
chatIdpela mesma regra do resto da base:wa_lidprimeiro, e — quando só houver telefone — passar pelas variantes dephoneLookupVariants/check-existsantes de desistir.Passos para reproduzir
wa_idno WhatsApp não tenha o nono dígito (número antigo). O CRM gravaphone_number = +55AA9BBBBCCCC(13 dígitos), mas o WhatsApp registra55AABBBBCCCC(12).chatIdvem pronto no webhook).GET /api/v1/cron/contact-avatars.{"scanned":1,"updated":0,"no_picture":1,"failed":0}.avatar_updated_até preenchido,avatar_storage_pathcontinuaNULL.chatIdque o cron monta.Medição na instalação
Mesmo contato, mesma sessão, três identificadores (números mascarados):
contactIdenviado ao WAHA55AA9BBBBCCCC@c.us— o que o cron monta (13 díg.){"profilePictureURL": null}55AABBBBCCCC@c.us— owa_idreal (12 díg.){"profilePictureURL": "https://pps.whatsapp.net/…"}<wa_lid>@lid— o LID, já presente emcontacts.wa_lid{"profilePictureURL": "https://pps.whatsapp.net/…"}O próprio WAHA aponta o identificador correto:
Repetir com
refresh=truenão muda o resultado. A linha do contato já tem owa_lidpreenchido, e ele funciona — o job apenas não o lê.Ambiente
Saída de
/api/v1/health:{"data":{"status":"healthy","version":"1.19.0", "checks":{"supabase":{"status":"ok","latency_ms":78}, "redis":{"status":"ok","latency_ms":13}, "waha":{"status":"ok","latency_ms":10}}}}Logs / observações
Resposta do job, depois de limpar
avatar_updated_atpara forçar o reprocessamento:Estado da linha em
contacts(números mascarados):Sem stack trace: o caminho percorrido é o de "sem foto", não o de erro — por isso
failed: 0e nada nos logs do app. É justamente o que torna o defeito silencioso em produção.Sugestão de correção
Trocar a
chatIdFromIdentitylocal porresolveWahaChatId(lib/waha/send.ts), incluindowa_lidno.select()da query. Isso alinha o cron às outras duas implementações e resolve o caso comum sem chamada extra ao WAHA. Para contatos que só tenham telefone, o caminho deresolve-contact-whatsapp-id.ts(check-exists+phoneLookupVariants) cobriria o restante.Se ajudar, abro um PR.