Skip to main content
Cada tenant liga a sua subconta (location) do GoHighLevel. Os tokens ficam no cofre (ghl.access, ghl.refresh, …) e nunca saem pela API. Tudo abaixo, exceto o callback e o webhook, usa a chave admin (X-Admin-Key) e exige ZAPMEOW_MASTER_KEY.

Variáveis

Conectar

  1. GET /admin/tenants/{id}/ghl/install-url?userType=Location devolve o link de autorização (vale 30 min). Company serve para instalação pela agência: o tenant precisa ter ghlLocationId, o ZapMeow troca o token da agência por um da subconta e renova os dois sozinho.
  2. O cliente abre o link e escolhe a subconta. A GHL volta em GET /ghl/oauth/callback?code&state. O state é assinado e amarra o retorno ao tenant; se a subconta autorizada não for a do tenant, nada é gravado.
  3. O token de acesso dura cerca de 24 h; o de renovação gira a cada uso e o novo é gravado antes de usar o acesso. Há renovação sob demanda (uma por vez por tenant), 401 renova e repete a chamada, e uma rotina a cada 10 min renova o que está perto de vencer. Se a GHL recusa a renovação (invalid_grant), a conexão vai para status: reauth e o cliente precisa reinstalar.
  4. PUT /admin/tenants/{id}/ghl/pit com {"value","locationId"} liga uma subconta por Private Integration Token (sem renovação). Serve para testar sem criar o app; não use para clientes.

Espelho

A carga inicial roda sozinha depois da instalação. Webhook: POST /ghl/webhook (URL única do app, em Advanced Settings). Confere o X-GHL-Signature (Ed25519, base64, sobre o corpo cru); sem assinatura válida, 401. Trata Opportunity* (cria, atualiza, apaga) e UNINSTALL (desconecta). O estágio vindo do webhook atualiza o espelho e dispara o evento GHLEvent para quem assinou com Manager.onGHL (motor de fluxo), com o estágio anterior. Se o espelho já tinha esse estágio (eco de um movimento feito pelas nossas tools), não há evento. Subconta desconhecida responde 200, para a GHL não repetir.
Pipelines e campos personalizados não têm webhook documentado pela GHL. Eles são recarregados no sync, e sozinhos (no máximo 1 vez por minuto) quando chega um estágio que o espelho não conhece. Campo novo só entra com POST .../ghl/sync.

Tools

GET /admin/tenants/{id}/ghl/tools lista nome, descrição, inputSchema e o modo atual. POST /admin/tenants/{id}/ghl/tools/{tool} com {"session","input","dryRun"} chama. Modo por tool, na seção tools da config (PUT /admin/tenants/{id}/config/tools, {"modes":{"gravar_campo":"executa"}}):
  • pede_ok (padrão): a chamada é validada e fica em zm_toolpending (202, com pendingId e o plano). Nada vai à GHL até POST .../ghl/pending/{pid}/approve ({"by"}); o plano é refeito na hora de aprovar. .../reject descarta. Aprovar duas vezes executa uma só.
  • executa: envia direto.
  • dryRun: true: modo sombra; devolve o plano sem enviar e sem criar pendência.
Toda chamada (sombra, pendente, executada, aprovada, rejeitada ou com erro) entra em zm_toollog (GET .../toollog) com modo, entrada e resultado. GET .../ghl/pending?status=pending lista os pedidos.

O que não é oficial ou não existe

  • Iniciar ligação pelo LC Phone: não há endpoint público (só dá para registrar ligações já feitas). Por isso não existe tool ligar_ghl; a ligação continua pelo /call do ZapMeow.
  • Criar pipeline ou estágio por API: não existe; o cliente monta na GHL e o ZapMeow só lê.
  • WhatsApp por QR (whatsmeow) dentro do ecossistema GHL: nenhum documento da GHL o autoriza nem o proíbe por nome; o risco é da Meta e da revisão do Marketplace. Manter só em instalação privada (até 5 agências) até perguntar por escrito à GHL.
  • Não testado contra uma subconta real: formato do customFields (field_value), eco do webhook e payload (plano ou dentro de data; os dois são aceitos) vêm da documentação e do OpenAPI, validados contra um mock.
  • Um único processo: o travamento do refresh é em memória. Com mais de uma réplica, dois refresh simultâneos podem invalidar o token.