Pular para o conteúdo principal

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.

Esse evento é disparado toda vez que um badge é atribuído a um usuário, seja por compra, atribuição manual no admin, ou via API.
O nome interno do evento (usado nos cadastros de webhook do Console) é user_received_badge.

Quando dispara

  • Admin atribui badge manualmente no painel
  • Compra externa via webhook do Hotmart/Kiwify dispara atribuição de badge
  • Usuário completa um curso e ganha badge automático
  • Você mesmo chama a API para atribuir

Quando NÃO dispara

  • Atribuição falha (ex: badge não existe no tenant)
  • Usuário foi soft-deleted

Payload

O payload é serializado em PascalCase e enviado no body do POST com Content-Type: application/json:
{
  "CustomerId": "01HQ0ABCDEF1234567890XYZ",
  "BadgeId": "01HQ4ABCDEF1234567890XYZ",
  "BadgeName": "Premium",
  "User": {
    "Id": "01HQ7Z3X4Y5Z6A7B8C9D0E1F2G",
    "Email": "maria@exemplo.com",
    "FirstName": "Maria",
    "LastName": "Silva",
    "DisplayName": "Maria Silva",
    "Username": "maria.silva",
    "PhoneNumber": "+5511999998888",
    "CreatedAt": "2026-04-12T14:32:01Z",
    "BadgeId": "01HQ4ABCDEF1234567890XYZ",
    "Badges": [
      "01HQ4ABCDEF1234567890XYZ",
      "01HQ4ZXYZ987654321FEDCBA"
    ]
  },
  "ReceivedAt": "2026-05-08T14:32:01Z"
}

Campos do payload

CampoTipoDescrição
CustomerIdGUIDID do tenant que originou o evento. Use para rotear quando seu endpoint recebe webhooks de múltiplos tenants.
BadgeIdGUIDID do badge atribuído.
BadgeNamestringNome configurado do badge (ex: Premium).
User.IdGUIDID do usuário que recebeu o badge.
User.EmailstringEmail do usuário.
User.FirstNamestringPrimeiro nome.
User.LastNamestringSobrenome.
User.DisplayNamestringNome de exibição.
User.UsernamestringNome de usuário (sem espaços).
User.PhoneNumberstringTelefone, quando informado.
User.CreatedAtISO 8601Quando o usuário foi criado no tenant.
User.BadgeIdGUID | nullBadge principal do usuário (compatibilidade — pode ser igual ao BadgeId do nível raiz).
User.BadgesGUID[]Lista completa de badges atribuídos ao usuário no momento do evento.
ReceivedAtISO 8601Quando a atribuição aconteceu no tenant.

Headers do request

HeaderDescrição
X-Cativa-SignatureAssinatura HMAC-SHA256 do disparo, no formato t=<unixTs>,v1=<hex>. Verifique antes de processar o evento.
X-Cativa-Execution-IdID único deste evento. Estável entre retries — use como chave de idempotência.
X-Cativa-Automation-IdID do listener configurado no Console (mesmo valor para todos os disparos do mesmo cadastro).
A explicação completa de como verificar X-Cativa-Signature (com exemplos em Node, Python, Go e C#) está em Cadastrando e verificando webhooks.

Receiver de exemplo (Express)

Esse exemplo verifica a assinatura HMAC, descarta requests com timestamp fora da janela anti-replay de 5 minutos, e processa só o que está autenticado:
import express from 'express';
import crypto from 'crypto';

const app = express();
const SECRET = process.env.CATIVA_WEBHOOK_SECRET; // whsec_...

app.post(
  '/webhooks/cativa',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    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 (!ts || !sig) return res.status(400).send('bad signature header');
    if (Math.abs(Date.now() / 1000 - ts) > 300) {
      return res.status(400).send('stale timestamp');
    }

    const expected = crypto
      .createHmac('sha256', SECRET)
      .update(`${ts}.${req.body.toString('utf8')}`)
      .digest('hex');

    const ok = crypto.timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(sig, 'hex')
    );
    if (!ok) return res.status(401).send('invalid signature');

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

    if (event.BadgeName === 'Premium') {
      // Sincroniza com seu sistema externo
      await grantAccessInExternalSystem(event.User.Id, event.User.Email);
    }

    // Responda 200 rápido — processe o resto em background
    res.status(200).send('ok');
  }
);

Idempotência

Use o header X-Cativa-Execution-Id recebido junto com o request para detectar duplicatas (mesmo executionId é enviado em todos os retries de um mesmo evento):
async function processBadgeEvent(executionId, payload) {
  if (await db.events.exists(executionId)) return;

  await db.transaction(async (tx) => {
    await applyBadgeLogic(tx, payload);
    await tx.events.insert({ id: executionId, processedAt: new Date() });
  });
}

Retries

Se seu endpoint falhar, a Cativa re-tenta na curva 30s → 5min → 30min → 2h → 6h → 24h (6 retries, 7 entregas no total, ~33h de cobertura). Códigos 400/401/403/404/410 são tratados como falhas permanentes — não há retry. Veja a tabela completa em Cadastrando e verificando webhooks.

Eventos relacionados

user_joined_group

Disparado quando o usuário entra num grupo (pode ser por badge).

Cadastrando webhooks

Como cadastrar listeners, verificar HMAC e lidar com retries.