Contatos
Hub persiste a lista de contatos do celular pareado em DB proprio. Vale como backup forense — se o chip e banido, voce ainda tem snapshot (nomes, telefones, fotos, bios).
Auth. Endpoints aceitam admin key (acesso total) ou Bearer App (escopado). Bearer App so ve contatos de chips em que o app tem app_chip_access.canReceive=true. Sem acesso → 403 contacts-access-denied.
Origem dos dados
3 caminhos populam a tabela contacts:
messaging-history.set— dispara apenas apos pareamento inicial. Traz a agenda inteira de uma vez.contacts.upsert/contacts.update— em tempo real conforme contatos sao adicionados/editados no celular.handleInbound— toda mensagem recebida enriquece o registro do remetente (phone_numberreal viakey.senderPn,notify, etc).
⚠️ Limitacao do WhatsApp moderno: sync inicial (
messaging-history.set) hoje entrega contatos so como LID puro (sem telefone) por privacidade Meta. Cruzamento LID↔telefone vem das mensagens recebidas. Pra um chip ja pareado, fazerrepaire a unica forma de re-disparar o sync inicial.
POST /v1/chips/:chipId/contacts/:jid/refresh-photo
Forca re-fetch da foto de perfil via Baileys. Util quando integradora desconfia que photo_media_id esta defasado e o WA nao disparou contacts.update automatico.
POST /v1/chips/{chipId}/contacts/{jid}/refresh-photoAuthorization: Bearer APP_KEYResposta 200:
{ "chip_id": "01HZTQCHIP...", "jid": "5511988887777@s.whatsapp.net", "photo_media_id": "01HZTQNEWPHOTO...", "changed": true}changed: true→ foto era diferente e o cache foi atualizado (webhookcontact.updatedreasonphotofoi disparado).changed: false→ foto identica (mesmo media_id).nullemphoto_media_id→ contato nao tem foto ou bloqueou visualizacao.
Erros:
403 contacts-access-denied— Bearer App semcanReceiveno chip404 contact-not-found— contato nao existe no chip503 chip-not-connected— chip offline (Baileys nao tem socket vivo)
GET /v1/chips/:chipId/contacts
Lista contatos com paginacao por cursor.
Query params
| Param | Tipo | Notas |
|---|---|---|
search | string opcional | match por nome/notify/phone_number/jid (case-insensitive) |
limit | int 1-200, default 50 | tamanho da pagina |
cursor | ULID opcional | id do ultimo item da pagina anterior |
Response
{ "data": [ { "id": "01HZTQ...", "chip_id": "...", "jid": "5511999998888@s.whatsapp.net", "lid": "12345678901234@lid", "phone_number": "5511999998888", "name": null, "notify": "Maria", "verified_name": null, "status_text": null, "has_photo": true, "photo_url": "/v1/chips/.../contacts/.../photo", "last_synced_at": "...", "removed_at": null, "created_at": "...", "updated_at": "..." } ], "next_cursor": "01HZTQ...", "total": 137}name (agenda) frequentemente e null — WA so envia em casos especificos (privacidade do operador). Use notify (pushName) como fallback.
photo_url aponta pro endpoint de stream — concatenar com base URL.
GET /v1/chips/:chipId/contacts/:jid
Detalhe de 1 contato. :jid precisa ser url-encoded (@ vira %40).
curl "$HUB/v1/chips/$CHIP_ID/contacts/5511999998888%40s.whatsapp.net" \ -H "Authorization: Bearer $ADMIN_KEY"Mesmo schema do item da lista.
404 contact-not-found se o JID nao existe no DB.
GET /v1/chips/:chipId/contacts/:jid/photo
Stream da foto de perfil. Lazy fetch: se ainda nao cacheou, hub baixa via Baileys, salva no MinIO, e responde.
curl "$HUB/v1/chips/$CHIP_ID/contacts/5511...%40s.whatsapp.net/photo" \ -H "Authorization: Bearer $ADMIN_KEY" \ -o foto.jpgHeaders de resposta:
Content-Type: image/jpegCache-Control: private, max-age=3600Cache local de 1h no cliente HTTP. No MinIO fica indefinidamente (ate proximo update detectado).
Erros
| Status | Code | Quando |
|---|---|---|
| 404 | contact-not-found | JID nao esta na tabela |
| 404 | (proprio body) | contato nao tem foto OU bloqueia visualizacao |
| 503 | chip-offline | chip nao esta connected, nao da pra fetch |
Estrategia pra app consumidora
- Listar contatos via
GET /v1/chips/:chipId/contacts?search=maria - Renderizar lista com
notify(ouname) + telefone - Na tela de detalhe, fazer
<img src="{photo_url}">direto — primeiro hit faz fetch async, subsequentes cacheiam - Pra cross-match com sua base interna: usar
phone_number(quando nao-vazio) oulid