Pular para o conteúdo principal
A maioria dos parceiros já tem um CRM (HubSpot, RD Station, Pipedrive, ActiveCampaign, Salesforce) que é a fonte de verdade dos contatos. Este guia mostra como manter a comunidade Cativa em sincronia com o CRM — o que dá pra fazer hoje, o que está em desenvolvimento e quais as decisões de design (chave natural, conflitos, idempotência) que você precisa tomar.

Cenário

Seu time de marketing usa HubSpot. Cada vez que um lead é qualificado e ganha a tag Pago Anual lá, você quer que essa pessoa apareça na sua comunidade Cativa com o badge Premium (que dá acesso ao grupo VIP, ao curso pago, etc.). Quando a tag é removida (ou o cliente cancela), o badge precisa sair. A Cativa é a fonte de verdade da comunidade (quem está no grupo, quem completou qual lição, quem postou o quê). O CRM é a fonte de verdade da relação comercial (lead, oportunidade, cliente, churn). A integração faz a ponte entre os dois.

Pré-requisitos

  1. API Key da Cativa — gerada no Console (Developers > API Keys). Veja Quick Start: API Key.
  2. Acesso de API ao seu CRM — token do HubSpot, da RD Station, etc. A Cativa não fornece SDK pra esses CRMs; use os SDKs oficiais (@hubspot/api-client, pipedrive-node-sdk, etc.).
  3. Um servidor de integração rodando do seu lado — pode ser um worker Node, um Cloud Function/Lambda, um job no Airflow, qualquer coisa que execute código com acesso aos dois lados.

Mapeamento conceitual

A pergunta central é: como reconhecer que o contato 1234 no HubSpot é o mesmo usuário 01HQ7Z3X4Y... na Cativa?
HubSpotCativa
Contact IDUser ID
EmailEmail (chave natural)
Tag Pago AnualBadge Premium
A chave natural recomendada é o email — ela está dos dois lados desde o cadastro. Você pode opcionalmente criar uma propriedade custom no HubSpot chamada cativa_user_id pra cachear o User ID Cativa após o primeiro casamento e evitar lookup por email a cada execução.
A Cativa não tem hoje um campo external_id no User que permita associar diretamente a chave do seu CRM. A chave natural é o email. Quando esse campo for adicionado ao modelo público, esta página será atualizada com a opção de upsert por external_id.

Dois fluxos possíveis

Quando usar: você não tem webhook do CRM disponível, ou os volumes são pequenos (até alguns milhares de contatos), ou um delay de minutos/horas é aceitável.Um worker do seu lado roda numa frequência (ex: a cada 15 minutos), lê o estado do CRM, lê o estado da Cativa, calcula o diff e aplica.
cron 15 min ─→ pull HubSpot ─→ pull Cativa ─→ diff ─→ apply
Prós: simples de implementar, fácil de debugar, fácil de fazer backfill. Contras: delay (não é real-time), gasto desnecessário de API calls quando nada mudou.

Descobrir um usuário existente na Cativa

Hoje há um endpoint público que você pode chamar com sua API Key: GET /social/v1/auth/me — retorna o usuário dono da chave (não busca usuário arbitrário). Use isso para sanity-check da sua credencial e descobrir o customer (tenant) ao qual a chave pertence.
curl https://apis.cativalab.digital/tenant/v1/auth/me \
  -H "Authorization: Bearer cativa_live_..."
Para buscar um usuário arbitrário pelo email (que é o caso da sincronização CRM), o endpoint público usando API Key está em desenvolvimento. Hoje, esse lookup usa um endpoint admin (/social/v1/admin/users/email/{email}) que não está disponível para API Keys de parceiro — apenas para o admin do tenant logado no Console.Workaround atual: alinhe com o time da Cativa em dev@cativa.digital para liberar o lookup do seu cenário, ou use o evento de webhook user_created (vide abaixo) para popular sua própria tabela de mapeamento email → Cativa User ID conforme novos usuários entram.

Estratégia recomendada hoje: tabela de mapeamento populada via webhook

Em vez de fazer lookup por email a cada sync, mantenha uma tabela do seu lado:
crm_cativa_mapping
─────────────────
hubspot_contact_id  TEXT
cativa_user_id      UUID
email               TEXT
last_synced_at      TIMESTAMP
E popule:
  1. Cadastre um listener Cativa pro evento user_created — toda vez que alguém entra na comunidade, o webhook chega com User.Id e User.Email. Insere/atualiza essa linha.
  2. Cadastre um listener pro evento user_received_badge — toda mudança de badge atualiza a linha.
Depois disso, seu worker de sync resolve emailcativa_user_id localmente, sem precisar bater na API da Cativa.

Criar usuário a partir do CRM

O endpoint público para parceiros criarem usuários via API Key está em desenvolvimento. Hoje, a criação acontece quando o próprio usuário se cadastra na comunidade (via Login com a Cativa, convite, ou link público de cadastro do tenant).Workarounds atuais:
  • Convite por email — gere um link de cadastro do tenant e envie via seu CRM (HubSpot Email, RD Station Email). Quando o lead clica e completa o cadastro, o evento user_created chega no seu webhook e você consegue dar o badge logo em seguida.
  • Importação em lote — para grandes massas iniciais (milhares de contatos), o time da Cativa consegue importar uma planilha via Console. Alinhe em dev@cativa.digital.
Quando o endpoint público for liberado, esta página será atualizada com cURL e exemplos.

Atribuir badge

A atribuição de badge via API Key de parceiro está em desenvolvimento. Hoje, atribuição/remoção de badges é feita:
  • Manualmente no Console do tenant (admin), ou
  • Via importação em massa (planilha), ou
  • Em produção, via fluxos automáticos da plataforma (compra paywall, conclusão de curso) — não via API direta de parceiro.
Quando o endpoint público estiver disponível, esta página vai mostrar o cURL completo. Pra liberar o seu caso enquanto isso, abra ticket em dev@cativa.digital.
Em código, planeje seu worker assumindo que a chamada vai existir. Esquema mental do que vai parecer:
// Quando o endpoint estiver disponível:
async function assignBadge(cativaUserId, badgeId) {
  const res = await fetch(
    `https://apis.cativalab.digital/tenant/v1/.../badges/${badgeId}/users/${cativaUserId}`,
    {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.CATIVA_API_KEY}` }
    }
  );
  if (!res.ok) throw new Error(`Badge assign failed: ${res.status}`);
}

async function removeBadge(cativaUserId, badgeId) {
  const res = await fetch(
    `https://apis.cativalab.digital/tenant/v1/.../badges/${badgeId}/users/${cativaUserId}`,
    {
      method: 'DELETE',
      headers: { Authorization: `Bearer ${process.env.CATIVA_API_KEY}` }
    }
  );
  if (!res.ok) throw new Error(`Badge remove failed: ${res.status}`);
}
A Cativa garante que atribuir o mesmo badge duas vezes é idempotente — o estado final é o mesmo. Mesma coisa pra remover badge que já não está atribuído. Isso simplifica retries no seu worker (ver Badges como permissão).

Esqueleto de worker pull-based

Mesmo com os endpoints de write em desenvolvimento, vale modelar o worker hoje. O que está dentro de assignBadge/removeBadge é o que precisa esperar.
import { Client as HubSpotClient } from '@hubspot/api-client';

const hubspot = new HubSpotClient({ accessToken: process.env.HUBSPOT_TOKEN });
const PREMIUM_BADGE_ID = process.env.CATIVA_PREMIUM_BADGE_ID;

async function syncContact(contact) {
  const email = contact.properties.email;
  if (!email) return; // sem email, não tem como casar

  // 1. Resolve email → Cativa User ID (tabela local populada via webhook)
  const mapping = await db.crmCativaMapping.findOne({ email });
  if (!mapping) {
    // Usuário ainda não está na Cativa. Envie um convite via CRM.
    await sendInviteEmail(email);
    return;
  }

  // 2. Estado desejado (HubSpot é a fonte de verdade)
  const shouldHavePremium = (contact.properties.lifecyclestage === 'customer'
    && contact.properties.deal_status === 'paid_annual');

  // 3. Estado atual (sua tabela local, atualizada via webhook user_received_badge)
  const hasPremium = await db.userBadges.exists({
    userId: mapping.cativaUserId,
    badgeId: PREMIUM_BADGE_ID
  });

  // 4. Aplica diff
  if (shouldHavePremium && !hasPremium) {
    await assignBadge(mapping.cativaUserId, PREMIUM_BADGE_ID);
  } else if (!shouldHavePremium && hasPremium) {
    await removeBadge(mapping.cativaUserId, PREMIUM_BADGE_ID);
  }
}

async function runSync() {
  let after;
  do {
    const page = await hubspot.crm.contacts.basicApi.getPage(100, after, [
      'email', 'lifecyclestage', 'deal_status'
    ]);
    for (const contact of page.results) {
      try {
        await syncContact(contact);
      } catch (err) {
        console.error(`Failed to sync ${contact.id}`, err);
        // Não interrompa o loop — registre e siga.
      }
    }
    after = page.paging?.next?.after;
  } while (after);
}

Tratar conflitos

CenárioO que fazer
Email mudou no CRM, ainda não mudou na CativaCativa é fonte do cativa_user_id. Se o email do contato HubSpot mudou e você não consegue mais casar pelo novo email, mantenha o cativa_user_id cacheado (no crm_cativa_mapping) e atualize a coluna email quando for diferente. O usuário Cativa ainda existe — o que mudou foi o ângulo do CRM.
Email do CRM nunca apareceu na CativaO contato HubSpot não tem (ainda) um usuário Cativa correspondente. Envie convite ou ignore até ele se cadastrar. Não tente “criar” forçadamente até o endpoint público estar disponível.
Mesmo email aparece em dois contatos no CRMDecisão do seu lado de qual contato é canônico (geralmente o mais recente, ou o que tem lifecyclestage mais avançado). Trate antes de chamar a Cativa.
Cliente cancelou no CRM mas tem badge ativo na CativaEsse é o caso que o sync de remoção resolve. O fluxo runSync acima cobre.
Badge atribuído por outra fonte (compra paywall) e CRM não sabeA Cativa pode atribuir badges por outros caminhos (compra paywall, conclusão de curso). Se seu CRM não for fonte de verdade pra esse badge específico, não remova o badge no diff — adicione um filtro do lado seu (ex: só sincronize badges que vieram via tag CRM).

Erros comuns

Quando o seu worker é a única autoridade sobre um badge e ele não vê motivo no CRM, ele remove. Mas se o badge foi atribuído por outra fonte (compra paywall, importação manual), o sync apaga indevidamente.Solução: mantenha uma lista de “badges gerenciados pelo CRM” no seu worker. Só atribua e remova esses. Badges fora da lista são ignorados pelo diff.
Em syncs grandes (milhares de contatos), você pode bater rate limit no CRM ou na Cativa. Implemente:
  • Backoff exponencial em 429 (a Cativa respeita Retry-After).
  • Paginação no CRM (ex: getPage(100, after, ...) no HubSpot).
  • Paralelismo controlado (ex: p-limit(5) no Node).
Se você só popula crm_cativa_mapping via webhook, qualquer disparo perdido vira lacuna. Por isso recomendamos:
  • Um reconcile job semanal que pega todos os contatos do CRM com email e tenta dar match com os usuários conhecidos.
  • Logging do X-Cativa-Execution-Id em todo webhook recebido — se faltar, dá pra abrir ticket de investigação.
Acontece quando o usuário usa email pessoal pra comprar e email corporativo pra entrar na comunidade (ou vice-versa). Não tem solução automática — você precisa uma propriedade adicional no CRM (ex: community_email) e usar essa pro lookup em vez do email principal.

Próximos passos

Subscrever webhooks Cativa

Configure listeners para user_created e user_received_badge para manter sua tabela de mapeamento atualizada.

Liberar acesso via compra externa

O caso específico de “compra externa → badge” tem padrões e cuidados próprios.