# Webhooks da Área de Membros

Este documento descreve como integrar e consumir os webhooks enviados pela plataforma TheMembers. Os webhooks permitem que sistemas de clientes recebam notificações em tempo real sobre eventos relevantes, como conclusão de aulas, emissão de certificados, publicação de conteúdo e inatividade de usuários.

***

## Visão Geral

A plataforma TheMembers envia requisições HTTP POST para os endpoints de webhook registrados pelos clientes sempre que determinados eventos ocorrem. O payload enviado está no formato JSON e contém informações detalhadas sobre o evento.

Características principais:

* Protocolo: HTTP POST
* Formato: JSON
* Autenticação: HMAC-SHA256
* Timeout: 30 segundos
* Tentativas: Até 3 tentativas em caso de falha
* Intervalo entre tentativas: 30s, 60s, 120s

***

## Autenticação e Segurança

Cada requisição enviada para o webhook do cliente contém um cabeçalho de autenticação HMAC para garantir a integridade e autenticidade dos dados:

* Cabeçalho: X-Webhook-Signature
* Valor: Assinatura HMAC-SHA256 do corpo da requisição, usando o client\_token do cliente
* Cabeçalho adicional: X-Webhook-Event — Nome do evento disparado

Como validar:

1. Calcule o HMAC-SHA256 do corpo da requisição recebida (JSON bruto, sem formatação extra) usando seu client\_token como chave.

Importante: O JSON é serializado usando as seguintes regras:

* Barras / não são escapadas (sem \\/)
* Caracteres Unicode não são escapados (ex: á ao invés de \u00e1)

2. Certifique-se de que sua serialização JSON corresponda a esse comportamento.
3. Codifique o resultado em hexadecimal (hash\_hmac com raw\_output = false).
4. Compare com o valor do cabeçalho X-Webhook-Signature.

{% hint style="info" %}
A assinatura só corresponderá se o JSON for serializado exatamente conforme as regras acima (sem barras ou unicode escapados).
{% endhint %}

Exemplo em PHP:

{% code title="webhook-validate.php" %}

```php
<?php

$body = file_get_contents('php://input');

$clientToken = 'seu_client_token_aqui';

$expectedSignature = hash_hmac('sha256', $body, $clientToken);

$providedSignature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

if (!hash_equals($expectedSignature, $providedSignature)) {
    http_response_code(401);
    exit('Assinatura inválida');
}

// Processar o payload
$payload = json_decode($body, true);
```

{% endcode %}

Exemplo em JavaScript (Node.js):

{% code title="server.js" %}

```javascript
const crypto = require('crypto');
const express = require('express');
const app = express();

app.use(express.json({
    verify: (req, res, buf) => {
        req.rawBody = buf.toString('utf8');
    }
}));

app.post('/webhook', (req, res) => {
    const clientToken = 'seu_client_token_aqui';
    const providedSignature = req.headers['x-webhook-signature'];
    const expectedSignature = crypto
        .createHmac('sha256', clientToken)
        .update(req.rawBody)
        .digest('hex');

    if (expectedSignature !== providedSignature) {
        return res.status(401).send('Assinatura inválida');
    }

    // Processar o payload
    const payload = req.body;
    console.log('Evento recebido:', payload.event);
    res.status(200).send('OK');
});

app.listen(3000, () => {
    console.log('Servidor webhook rodando na porta 3000');
});
```

{% endcode %}

***

## Exemplo de Assinatura

Abaixo está um exemplo de corpo de requisição de webhook e o valor esperado para o cabeçalho X-Webhook-Signature, assumindo que o client\_token seja mySecretKey123.

Corpo da Requisição de Exemplo:

```json
{
  "type": "class_history",
  "event": "class.completed",
  "created": "2025-01-13T14:30:00.000000Z",
  "data": {
    "datetime": "2025-01-13T14:25:00.000000Z",
    "platform_id": 1,
    "platform_name": "Minha Plataforma",
    "student": {
      "id": 12345,
      "reference_id": "EXT-001",
      "name": "João da Silva",
      "email": "joao@exemplo.com",
      "phone": "+5511999999999",
      "document": "12345678900",
      "gender": "male"
    },
    "course": {
      "id": 100,
      "name": "Curso de Programação",
      "module": {
        "id": 10,
        "name": "Módulo 1 - Fundamentos",
        "class": {
          "id": 1,
          "name": "Introdução ao PHP"
        }
      }
    }
  }
}
```

Assinatura Esperada

Para o corpo acima e client\_token mySecretKey123, o valor do cabeçalho X-Webhook-Signature será (exemplo ilustrativo):

4a3d8c1e2f5b6a7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c

Nota: A assinatura acima é ilustrativa. O valor real dependerá da serialização exata do JSON e da chave secreta.

Importante: A assinatura só corresponderá se o JSON for serializado exatamente como acima, sem barras ou unicode escapados.

***

## Eventos Enviados

{% stepper %}
{% step %}

### Conclusão de Aula (class.completed)

Enviado quando um aluno conclui uma aula (progresso = 100% ou marcada como finalizada).

* Tipo de Evento: class.completed
* Tipo de Payload: class\_history

Exemplo de Payload:

```json
{
  "type": "class_history",
  "event": "class.completed",
  "created": "2025-01-13T14:30:00.000000Z",
  "data": {
    "datetime": "2025-01-13T14:25:00.000000Z",
    "platform_id": 1,
    "platform_name": "Minha Plataforma",
    "student": {
      "id": 12345,
      "reference_id": "EXT-001",
      "name": "João da Silva",
      "email": "joao@exemplo.com",
      "phone": "+5511999999999",
      "document": "12345678900",
      "gender": "male"
    },
    "course": {
      "id": 100,
      "name": "Curso de Programação",
      "module": {
        "id": 10,
        "name": "Módulo 1 - Fundamentos",
        "class": {
          "id": 1,
          "name": "Introdução ao PHP"
        }
      }
    }
  }
}
```

Campos principais:

* type (string): Tipo do payload, sempre class\_history para este evento
* event (string): Nome do evento, sempre class.completed
* created (string): Data/hora de criação do evento (ISO 8601)
* data (object): Dados do evento, incluindo:
  * datetime (string): Data/hora em que a aula foi concluída (ISO 8601)
  * platform\_id (integer)
  * platform\_name (string)
  * student (object): id, reference\_id, name, email, phone, document, gender
  * course (object): id, name, module (com id, name, class com id e name)
    {% endstep %}

{% step %}

### Publicação de Aula (class.published)

Enviado quando uma nova aula é publicada na plataforma (no momento da criação ou publicação manual).

* Tipo de Evento: class.published
* Tipo de Payload: class

Exemplo de Payload:

```json
{
  "type": "class",
  "event": "class.published",
  "created": "2025-01-13T14:30:00.000000Z",
  "data": {
    "datetime": "2025-01-13T14:30:00.000000Z",
    "platform_id": 1,
    "platform_name": "Minha Plataforma",
    "course": {
      "id": 100,
      "name": "Curso de Programação",
      "module": {
        "id": 10,
        "name": "Módulo 1 - Fundamentos",
        "class": {
          "id": 1,
          "name": "Introdução ao PHP"
        }
      }
    }
  }
}
```

Campos principais:

* type (string): Tipo do payload, sempre class para este evento
* event (string): Nome do evento, sempre class.published
* created (string): Data/hora de criação do evento (ISO 8601)
* data (object): Inclui datetime, platform\_id, platform\_name, course (id, name, status, module com id, name, status, class com id e name)
  {% endstep %}

{% step %}

### Certificado Emitido (certificate.issued)

Enviado quando um certificado é emitido para um aluno.

* Tipo de Evento: certificate.issued
* Tipo de Payload: certificate

Exemplo de Payload:

```json
{
  "type": "certificate",
  "event": "certificate.issued",
  "created": "2025-01-13T14:30:00.000000Z",
  "data": {
    "datetime": "2025-01-13T14:30:00.000000Z",
    "platform_id": 1,
    "platform_name": "Minha Plataforma",
    "student": {
      "id": 12345,
      "reference_id": "EXT-001",
      "name": "João da Silva",
      "email": "joao@exemplo.com",
      "phone": "+5511999999999",
      "document": "12345678900",
      "gender": "male"
    },
    "course": {
      "id": 100,
      "name": "Curso de Programação"
    },
    "certificate": {
      "id": 50,
      "name": "Certificado de Conclusão"
    }
  }
}
```

Campos principais:

* type (string): Tipo do payload, sempre certificate para este evento
* event (string): Nome do evento, sempre certificate.issued
* created (string): Data/hora de criação do evento (ISO 8601)
* data (object): Inclui datetime, platform\_id, platform\_name, student (id, reference\_id, name, email, phone, document, gender), course (id, name), certificate (id, name)
  {% endstep %}

{% step %}

### Usuário Inativo por 7 Dias (user.inactive\_for\_7\_days)

Enviado quando um usuário está inativo por exatamente 7 dias (sem login nos últimos 7 dias ou sem login desde a criação da conta).

Observação: Usuários inativos que permanecem inativos continuarão recebendo este evento a cada 7 dias.

* Tipo de Evento: user.inactive\_for\_7\_days
* Tipo de Payload: login\_history

Exemplo de Payload:

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "login_history",
  "event": "user.inactive_for_7_days",
  "created": "2025-01-13T14:30:00.000000Z",
  "data": {
    "datetime": "2025-01-06T14:30:00.000000Z",
    "inactive_days": 7,
    "platform_id": 1,
    "platform_name": "Minha Plataforma",
    "student": {
      "id": 12345,
      "reference_id": "EXT-001",
      "name": "João da Silva",
      "email": "joao@exemplo.com",
      "phone": "+5511999999999",
      "document": "12345678900",
      "gender": "female"
    }
  }
}
```

Campos principais:

* id (string): UUID único do evento
* type (string): Tipo do payload, sempre login\_history para este evento
* event (string): Nome do evento, sempre user.inactive\_for\_7\_days
* created (string): Data/hora de criação do evento (ISO 8601)
* data (object): Inclui datetime, inactive\_days, platform\_id, platform\_name, student (id, reference\_id, name, email, phone, document, gender)
  {% endstep %}
  {% endstepper %}

***

## Formato do Payload

* Todas as requisições são enviadas com o cabeçalho Content-Type: application/json
* Campos de data/hora seguem o padrão ISO 8601 (ex: 2025-01-13T14:30:00.000000Z)
* Campos podem conter valores null quando não aplicável
* O telefone é formatado no padrão E.164 quando disponível (ex: +5511999999999)
* Documentos (CPF/CNPJ) são retornados apenas com números, sem formatação

***

## Boas Práticas e Observações

Segurança

* Sempre valide a assinatura HMAC antes de processar o payload
* Mantenha seu client\_token seguro e não o compartilhe
* O client\_token é único para cada URL de webhook registrado
* Use HTTPS em seu endpoint de webhook

Resposta

* Responda rapidamente com HTTP 200 para evitar reenvio do evento
* Processe o evento de forma assíncrona se necessário
* Não execute operações demoradas na thread principal

Confiabilidade

* Implemente idempotência - o mesmo evento pode ser enviado mais de uma vez
* Registre logs de eventos recebidos para facilitar troubleshooting
* O endpoint deve estar disponível publicamente e aceitar requisições HTTPS

Tratamento de Erros

* Em caso de falha no envio, o sistema tentará reenviar o evento até 3 vezes
* Intervalos entre tentativas: 30s, 60s, 120s
* Após 3 falhas, o evento será marcado como falho e não será reenviado

Timeout

* O timeout da requisição é de 30 segundos
* Certifique-se de que seu endpoint responda dentro desse tempo

Performance

* O webhook pode receber múltiplos eventos em sequência
* Processe cada evento de forma independente e idempotente
* Considere usar filas para processar eventos de forma assíncrona


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://documentation.themembers.dev.br/webhooks/webhooks-da-area-de-membros.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
