> ## 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.

# user_received_badge

> Disparado quando um usuário recebe um badge — sua chance de sincronizar permissões externas.

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.

<Note>
  O nome interno do evento (usado nos cadastros de webhook do Console) é `user_received_badge`.
</Note>

## 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`:

```json theme={null}
{
  "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

| Campo              | Tipo         | Descrição                                                                                                     |
| ------------------ | ------------ | ------------------------------------------------------------------------------------------------------------- |
| `CustomerId`       | GUID         | ID do tenant que originou o evento. Use para rotear quando seu endpoint recebe webhooks de múltiplos tenants. |
| `BadgeId`          | GUID         | ID do badge atribuído.                                                                                        |
| `BadgeName`        | string       | Nome configurado do badge (ex: `Premium`).                                                                    |
| `User.Id`          | GUID         | ID do usuário que recebeu o badge.                                                                            |
| `User.Email`       | string       | Email do usuário.                                                                                             |
| `User.FirstName`   | string       | Primeiro nome.                                                                                                |
| `User.LastName`    | string       | Sobrenome.                                                                                                    |
| `User.DisplayName` | string       | Nome de exibição.                                                                                             |
| `User.Username`    | string       | Nome de usuário (sem espaços).                                                                                |
| `User.PhoneNumber` | string       | Telefone, quando informado.                                                                                   |
| `User.CreatedAt`   | ISO 8601     | Quando o usuário foi criado no tenant.                                                                        |
| `User.BadgeId`     | GUID \| null | Badge principal do usuário (compatibilidade — pode ser igual ao `BadgeId` do nível raiz).                     |
| `User.Badges`      | GUID\[]      | Lista completa de badges atribuídos ao usuário no momento do evento.                                          |
| `ReceivedAt`       | ISO 8601     | Quando a atribuição aconteceu no tenant.                                                                      |

## Headers do request

| Header                   | Descrição                                                                                                       |
| ------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `X-Cativa-Signature`     | Assinatura HMAC-SHA256 do disparo, no formato `t=<unixTs>,v1=<hex>`. **Verifique antes de processar o evento.** |
| `X-Cativa-Execution-Id`  | ID único deste evento. Estável entre retries — use como chave de idempotência.                                  |
| `X-Cativa-Automation-Id` | ID 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](/pt-BR/webhooks/subscribing-and-verifying#verificando-a-assinatura-hmac).

## 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:

```js theme={null}
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):

```js theme={null}
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](/pt-BR/webhooks/subscribing-and-verifying#retries-e-falhas-permanentes).

## Eventos relacionados

<CardGroup cols={2}>
  <Card title="user_joined_group" icon="users" href="/pt-BR/webhooks/events/user-joined-group">
    Disparado quando o usuário entra num grupo (pode ser por badge).
  </Card>

  <Card title="Cadastrando webhooks" icon="webhook" href="/pt-BR/webhooks/subscribing-and-verifying">
    Como cadastrar listeners, verificar HMAC e lidar com retries.
  </Card>
</CardGroup>
