> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cativa.digital/llms.txt
> Use this file to discover all available pages before exploring further.

# Liberar acesso via compra externa

> Hotmart, Kiwify, Eduzz ou Stripe — receba o webhook de compra e atribua o badge correto na Cativa.

Você vende um curso ou produto digital num gateway externo (Hotmart, Kiwify, Eduzz, Stripe Checkout) e quer que a compra **automaticamente** libere acesso a um grupo, curso ou espaço dentro da sua comunidade Cativa. Este guia mostra a arquitetura recomendada, o que dá pra fazer hoje e o que está em desenvolvimento.

## Cenário

Cliente compra **"Curso Premium"** na Hotmart. Em segundos, ele recebe acesso ao grupo **"Alunos Premium"** e ao curso na sua comunidade Cativa.

A ponte entre os dois lados é o conceito de [badge como permissão](/pt-BR/concepts/badges-as-permissions): o badge `Premium` é configurado no Console como requisito de acesso ao grupo e ao curso. Quando o usuário ganha o badge, o acesso aparece automaticamente. Quando perde, some.

## Pré-requisitos

1. **API Key da Cativa** — gerada no Console (Developers > API Keys). Veja [Quick Start: API Key](/pt-BR/get-started/quickstart-api-key).
2. **Badge configurado no Console** — crie o badge `Premium` (ou o nome do seu produto) e configure-o como requisito de acesso no grupo/curso correspondente. Veja [Badges como permissão](/pt-BR/concepts/badges-as-permissions).
3. **Webhook de compra do seu gateway** — Hotmart, Kiwify, Eduzz e Stripe disparam webhook pra um endpoint do **seu servidor** quando uma compra é confirmada. **Não aponte o webhook do gateway diretamente pra Cativa** — você precisa de um servidor proxy que recebe, valida e traduz o evento.
4. **Email confiável do comprador** — todos os gateways enviam o email no payload da compra. Esse é o ponto-de-junção com o usuário Cativa.

## Arquitetura do fluxo

```
   Cliente compra na Hotmart
            │
            ▼
   Webhook Hotmart ──► Seu servidor (proxy) ──► API Cativa (atribui badge)
                                                       │
                                                       ▼
                                         Webhook Cativa user_received_badge
                                                       │
                                                       ▼
                                              Email/CRM/Analytics
```

Você é o intermediário entre o gateway e a Cativa. Isso te dá controle pra:

* Validar o webhook do gateway (cada um tem assinatura própria — confira a doc do gateway).
* Tratar idempotência (Hotmart pode disparar o mesmo webhook duas vezes).
* Logar o purchase ID do gateway pra auditoria/conciliação.
* Decidir o badge correto baseado em qual produto foi comprado.

E você também pode opcionalmente subscrever o webhook **da Cativa** `user_received_badge` pra disparar email de boas-vindas, atualizar CRM, ou registrar evento no analytics.

## Implementação

<Steps>
  <Step title="Receber o webhook do gateway">
    Cada gateway tem seu próprio formato de webhook e seu próprio mecanismo de assinatura. Configure o webhook **no painel do gateway** apontando pra um endpoint do seu servidor (ex: `https://meuapp.com/webhooks/hotmart`).

    A Cativa **não documenta** o formato do webhook desses gateways — consulte a doc oficial:

    * [Hotmart](https://developers.hotmart.com/docs/pt-BR/v1/webhook/)
    * [Kiwify](https://docs.kiwify.com.br/produto/webhooks)
    * [Eduzz](https://developers.eduzz.com/api/webhooks)
    * [Stripe](https://stripe.com/docs/webhooks)

    Esqueleto do receiver (Express, exemplo Hotmart):

    ```js theme={null}
    import express from 'express';

    const app = express();

    app.post(
      '/webhooks/hotmart',
      express.json(),
      async (req, res) => {
        // 1. Verifique a assinatura do gateway aqui (seguindo a doc do gateway)

        const event = req.body;
        // Hotmart usa events do tipo "PURCHASE_APPROVED", "PURCHASE_CANCELED", etc.

        if (event.event === 'PURCHASE_APPROVED') {
          await handlePurchase({
            email: event.data.buyer.email,
            productId: event.data.product.id,
            purchaseId: event.data.purchase.transaction
          });
        }

        if (event.event === 'PURCHASE_CANCELED' ||
            event.event === 'PURCHASE_REFUNDED' ||
            event.event === 'PURCHASE_CHARGEBACK') {
          await handleCancellation({
            email: event.data.buyer.email,
            productId: event.data.product.id,
            purchaseId: event.data.purchase.transaction
          });
        }

        // Responda 2xx logo — processe em background se possível.
        res.status(200).send('ok');
      }
    );
    ```

    <Warning>
      Sempre **verifique a assinatura do webhook do gateway** antes de confiar nos dados. Sem isso, qualquer um que descobrir sua URL pode liberar badges arbitrários.
    </Warning>
  </Step>

  <Step title="Identificar o usuário na Cativa pelo email">
    Hoje há duas situações:

    **Caso A — o comprador já tem conta na Cativa:** você precisa achar o `User.Id` dele.

    <Note>
      O endpoint público para parceiros buscarem usuário arbitrário pelo email **está em desenvolvimento**. Hoje, esse lookup usa um endpoint admin que não está disponível para API Keys de parceiro.

      **Workaround recomendado:** mantenha uma tabela local `email → cativa_user_id` populada pelo webhook `user_created` da Cativa. Toda vez que alguém entra na comunidade, você grava a linha. Quando a compra chegar, faz lookup local sem bater na API.
    </Note>

    ```js theme={null}
    async function resolveCativaUserId(email) {
      const row = await db.userMap.findOne({ email });
      return row?.cativaUserId ?? null;
    }
    ```

    **Caso B — o comprador ainda não tem conta na Cativa:** você precisa fazer ele se cadastrar primeiro.

    <Note>
      Criação de usuário direto via API Key de parceiro **está em desenvolvimento**. Workaround atual: envie um email com o link de cadastro do tenant + um lembrete de que o badge será atribuído assim que ele entrar. Quando ele se cadastrar, o webhook `user_created` chega no seu servidor e você completa o fluxo (ver step 4).
    </Note>

    ```js theme={null}
    async function inviteBuyer({ email, productId, purchaseId }) {
      // Salva a intenção de atribuição pra completar quando user_created chegar
      await db.pendingGrants.insert({
        email,
        productId,
        purchaseId,
        createdAt: new Date()
      });

      // Envia email com link de cadastro do tenant
      await mailer.send({
        to: email,
        subject: 'Seu acesso ao Curso Premium',
        text: `Cadastre-se em https://{customerName}.cativa.digital/signup?email=${encodeURIComponent(email)} para liberar seu acesso.`
      });
    }
    ```
  </Step>

  <Step title="Atribuir o badge">
    Mapeie o `productId` do gateway pro `badgeId` da Cativa configurado no Console.

    <Note>
      A atribuição de badge via API Key de parceiro **está em desenvolvimento**. Hoje, atribuição/remoção de badges é feita no Console (manualmente ou via importação) ou via fluxos automáticos da plataforma. Quando o endpoint público estiver disponível, esta página será atualizada com o cURL completo. Pra liberar o seu caso enquanto isso, abra ticket em [dev@cativa.digital](mailto:dev@cativa.digital).
    </Note>

    Esquema mental do que vai parecer:

    ```js theme={null}
    const PRODUCT_TO_BADGE = {
      'hotmart_product_123': 'badge_uuid_premium',
      'hotmart_product_456': 'badge_uuid_mentoria_2026'
    };

    async function handlePurchase({ email, productId, purchaseId }) {
      const badgeId = PRODUCT_TO_BADGE[productId];
      if (!badgeId) {
        console.warn(`No badge mapped for product ${productId}`);
        return;
      }

      const cativaUserId = await resolveCativaUserId(email);
      if (!cativaUserId) {
        await inviteBuyer({ email, productId, purchaseId });
        return;
      }

      // Quando o endpoint estiver disponível:
      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}`);

      await db.purchases.insert({
        purchaseId,
        cativaUserId,
        badgeId,
        grantedAt: new Date()
      });
    }
    ```

    A Cativa garante que **atribuir o mesmo badge duas vezes é idempotente** (ver [Badges como permissão](/pt-BR/concepts/badges-as-permissions)) — então retries causados por timeout ou re-disparo do gateway não criam efeito duplicado.
  </Step>

  <Step title="(Opcional) Reagir ao webhook user_received_badge">
    A Cativa dispara o webhook `user_received_badge` toda vez que um badge é atribuído (não importa se foi via API, Console, ou outro fluxo). Cadastre um listener para ele se você quer:

    * Mandar email de boas-vindas com link pra comunidade
    * Atualizar o status do contato no CRM
    * Registrar conversão no analytics

    Veja [Cadastrando e verificando webhooks](/pt-BR/webhooks/subscribing-and-verifying) para o passo-a-passo de cadastro do listener e verificação HMAC. O payload do evento está em [user\_received\_badge](/pt-BR/webhooks/events/user-received-badge).

    Esqueleto do receiver:

    ```js theme={null}
    import express from 'express';
    import crypto from 'crypto';

    const SECRET = process.env.CATIVA_WEBHOOK_SECRET; // whsec_...

    app.post(
      '/webhooks/cativa',
      express.raw({ type: 'application/json' }),
      async (req, res) => {
        // Verificação HMAC — ver /webhooks/subscribing-and-verifying
        const sigHeader = req.header('X-Cativa-Signature') ?? '';
        const parts = Object.fromEntries(
          sigHeader.split(',').map(p => p.split('='))
        );
        const ts = Number(parts.t);
        const sig = parts.v1;
        if (Math.abs(Date.now() / 1000 - ts) > 300) return res.status(400).end();
        const expected = crypto
          .createHmac('sha256', SECRET)
          .update(`${ts}.${req.body.toString('utf8')}`)
          .digest('hex');
        if (!crypto.timingSafeEqual(
          Buffer.from(expected, 'hex'),
          Buffer.from(sig, 'hex')
        )) return res.status(401).end();

        const event = JSON.parse(req.body.toString('utf8'));

        if (event.BadgeName === 'Premium') {
          await sendWelcomeEmail(event.User.Email, event.User.FirstName);
          await analytics.track('Purchase Activated', {
            email: event.User.Email,
            badge: event.BadgeName
          });
        }

        res.status(200).send('ok');
      }
    );
    ```
  </Step>
</Steps>

## Cancelamento e chargeback

Quando o gateway cancela ou faz chargeback, você quer **remover o badge** pra revogar o acesso.

```js theme={null}
async function handleCancellation({ email, productId, purchaseId }) {
  const badgeId = PRODUCT_TO_BADGE[productId];
  if (!badgeId) return;

  const cativaUserId = await resolveCativaUserId(email);
  if (!cativaUserId) return;

  // Quando o endpoint estiver disponível:
  await fetch(
    `https://apis.cativalab.digital/tenant/v1/.../badges/${badgeId}/users/${cativaUserId}`,
    {
      method: 'DELETE',
      headers: { Authorization: `Bearer ${process.env.CATIVA_API_KEY}` }
    }
  );

  await db.purchases.update({ purchaseId }, { revokedAt: new Date() });
}
```

<Note>
  A remoção de badge via API Key de parceiro **está no mesmo waitlist** da atribuição. Mesmo workaround: hoje é feito no Console; o endpoint público sai em breve.
</Note>

Remover o badge é o suficiente — o acesso ao grupo, curso e espaços atrelados ao badge some imediatamente.

## Erros comuns

<AccordionGroup>
  <Accordion title="Comprador não tem cadastro Cativa, ficou com a compra paga e sem acesso">
    Esse é o caso mais comum. O cliente comprou na Hotmart antes de entrar na sua comunidade.

    **Solução:** o step 2 acima cobre — registre a intenção em `pendingGrants` e dispare o convite. Subscreva o webhook `user_created` e, quando o cadastro acontecer, complete a atribuição:

    ```js theme={null}
    // Listener do user_created
    if (event.event === 'user_created') {
      const pending = await db.pendingGrants.findAll({ email: event.User.Email });
      for (const grant of pending) {
        await handlePurchase({
          email: event.User.Email,
          productId: grant.productId,
          purchaseId: grant.purchaseId
        });
        await db.pendingGrants.delete({ id: grant.id });
      }
    }
    ```
  </Accordion>

  <Accordion title="Email diferente entre o gateway e a Cativa do mesmo usuário">
    Acontece quando o cliente compra com email pessoal e entra na comunidade com email corporativo. Não tem solução automática.

    **Solução:** ofereça uma página "Já comprei, mas estou logado com outro email" no seu app, onde o cliente informa o email da compra. Você valida o purchase ID localmente e atribui o badge pro usuário **logado** (não pro email da compra).
  </Accordion>

  <Accordion title="Webhook do gateway disparou duas vezes — usuário ganhou o badge duas vezes?">
    A atribuição de badge na Cativa é idempotente: aplicar o mesmo badge duas vezes resulta no mesmo estado final. Mas por garantia, salve o `purchaseId` do gateway numa tabela local e cheque antes:

    ```js theme={null}
    if (await db.purchases.exists({ purchaseId })) {
      return; // já processado
    }
    ```

    Isso também ajuda a auditar/conciliar mais tarde (ex: relatório financeiro vs concessões).
  </Accordion>

  <Accordion title="Gateway oferece &#x22;assinatura recorrente&#x22; — como tratar renovação mensal?">
    Cada gateway dispara webhook quando a renovação é cobrada com sucesso (ex: Hotmart `SUBSCRIPTION_CHARGE_SUCCESS`). Trate como um `handlePurchase` idempotente — re-aplica o badge (sem efeito se já está). Se a renovação falha (ex: cartão recusado), trate como `handleCancellation`.
  </Accordion>

  <Accordion title="Cliente reembolsou em < 7 dias mas continuou usando a comunidade">
    Você precisa **reagir ao chargeback rapidamente** — o webhook do gateway chega, você remove o badge na hora, acesso aos recursos atrelados some. Não confie em job batch noturno pra isso.
  </Accordion>

  <Accordion title="Múltiplos badges para um mesmo produto (curso + bônus)">
    Mapeie um produto pra **vários badges** se necessário. Exemplo: produto `Curso Premium` libera tanto `Premium` (acesso ao curso) quanto `Mentoria-2026` (acesso ao grupo de mentoria). Faça duas atribuições no `handlePurchase`.
  </Accordion>
</AccordionGroup>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Webhooks da Cativa" icon="webhook" href="/pt-BR/webhooks/subscribing-and-verifying">
    Cadastre listeners para `user_created` (cobrir caso "comprou antes de cadastrar") e `user_received_badge` (disparar ações pós-acesso).
  </Card>

  <Card title="Sincronizar membros do CRM" icon="arrows-rotate" href="/pt-BR/guides/sync-members-from-crm">
    Se você também usa CRM, combine este fluxo com sincronização de tags pra ter um único hub de permissões.
  </Card>
</CardGroup>
