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

# Eventos de Webhook de Entidades

> Receba notificações em tempo real quando entidades são criadas, atualizadas ou seu status muda — com eventos webhook gu1 para integração downstream em tempo.

## Visão Geral

Os eventos de webhook de entidades permitem que você receba notificações em tempo real quando entidades (pessoas, empresas, dispositivos, etc.) são criadas, atualizadas ou seu status muda na plataforma Gu1. Esses eventos permitem que você mantenha seus sistemas sincronizados com Gu1 e automatize fluxos de trabalho baseados em mudanças no ciclo de vida das entidades.

## Por Que Usar Eventos de Entidades?

<CardGroup cols={2}>
  <Card title="Sincronização em Tempo Real" icon="arrows-rotate">
    Mantenha seu banco de dados sincronizado com dados de entidades Gu1
  </Card>

  <Card title="Fluxos de Trabalho Automatizados" icon="robot">
    Acione ações quando o status da entidade mudar
  </Card>

  <Card title="Trilha de Auditoria" icon="list-check">
    Rastreie todas as mudanças de entidades para conformidade
  </Card>

  <Card title="Eficiente" icon="gauge-high">
    Não é necessário consultar a API para atualizações
  </Card>
</CardGroup>

## Eventos Disponíveis

### entity.created

Acionado quando uma nova entidade é criada no Gu1.

**Quando dispara**:

* Uma nova pessoa, empresa, dispositivo ou outra entidade é criada via `POST /entities`

**Filtros disponíveis**:

* `entityTypes`: Receber apenas eventos para tipos de entidades específicos (por exemplo, `["person", "company"]`)

### entity.updated

Acionado quando os dados de uma entidade são atualizados (excluindo mudanças de status).

**Quando dispara**:

* Informações da entidade são atualizadas via `PATCH /entities/:id`
* Mudanças em nome, atributos, dados da entidade, ID fiscal, etc.

**Nota**: Mudanças de status acionam `entity.status_changed` em vez disso.

**Filtros disponíveis**:

* `entityTypes`: Receber apenas eventos para tipos de entidades específicos

### entity.status\_changed

Acionado quando o status de uma entidade muda.

**Quando dispara**:

* Transições de status de entidade (por exemplo, `under_review` → `active`, `active` → `blocked`)
* Atualizações de status via `PATCH /entities/:id` ou ações de conformidade automatizadas

**Filtros disponíveis**:

* `entityTypes`: Filtrar por tipo de entidade
* `statusChanges.from`: Acionar apenas quando mudar DE um status específico
* `statusChanges.to`: Acionar apenas quando mudar PARA um status específico

<Note>
  As organizações configuradas explicitamente para receber o webhook legacy plano de entidades
  também recebem `riskScore` e `documentNumber` no nível superior do payload. São campos
  aditivos; os status legacy como `IN_PROGRESS` e `APPROVED` permanecem inalterados.
</Note>

### entity.country\_activation\_changed

Acionado quando o **status operacional de ativação por país** de um merchant muda.

**Quando dispara**:

* `PATCH /entities/:id/country-activations/:countryCode` com um status novo (não em repetições idempotentes)

**Status**: `deactivated`, `activation_requested`, `activation_in_progress`, `activated`. Transições são livres.

**Nota**: É apenas um flag operacional. Os dados da entidade não mudam — reconsulte com `GET /entities/:id` se necessário.

**Filtros disponíveis**:

* `entityTypes`: Filtrar por tipo de entidade (ex.: `["company"]`)

## Exemplos de Payload de Eventos

### entity.created

```json theme={null}
{
  "event": "entity.created",
  "timestamp": "2025-01-07T10:00:00.000Z",
  "payload": {
    "entity": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "customer_abc123",
      "name": "John Doe",
      "type": "person",
      "taxId": "123-45-6789",
      "countryCode": "US",
      "status": "under_review",
      "isClient": false,
      "attributes": {
        "email": "john.doe@example.com",
        "phone": "+1234567890",
        "customerTier": "premium"
      },
      "entityData": {
        "person": {
          "firstName": "John",
          "lastName": "Doe",
          "dateOfBirth": "1990-01-15",
          "nationality": "US",
          "occupation": "Software Engineer"
        }
      },
      "createdAt": "2025-01-07T10:00:00.000Z",
      "updatedAt": "2025-01-07T10:00:00.000Z"
    },
    "createdBy": "user_xyz789",
    "metadata": {
      "source": "api",
      "userAgent": "Mozilla/5.0...",
      "ipAddress": "192.168.1.100"
    }
  }
}
```

**Campos Chave**:

* `entity`: Objeto de entidade completo com todos os dados
* `entity.externalId`: Seu identificador único para a entidade
* `entity.type`: Tipo de entidade (person, company, device, etc.)
* `entity.status`: Status atual (under\_review, active, blocked, etc.)
* `createdBy`: ID do usuário que criou a entidade
* `metadata`: Contexto adicional sobre a criação

### entity.updated

```json theme={null}
{
  "event": "entity.updated",
  "timestamp": "2025-01-07T10:05:00.000Z",
  "payload": {
    "entity": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "customer_abc123",
      "name": "John Doe",
      "type": "person",
      "taxId": "123-45-6789",
      "countryCode": "US",
      "status": "active",
      "isClient": false,
      "attributes": {
        "email": "john.doe@newemail.com",
        "phone": "+1234567890",
        "customerTier": "premium"
      },
      "entityData": {
        "person": {
          "firstName": "John",
          "lastName": "Doe",
          "dateOfBirth": "1990-01-15",
          "nationality": "US",
          "occupation": "Senior Software Engineer"
        }
      },
      "updatedAt": "2025-01-07T10:05:00.000Z"
    },
    "changes": {
      "attributes.email": {
        "old": "john.doe@example.com",
        "new": "john.doe@newemail.com"
      },
      "entityData.person.occupation": {
        "old": "Software Engineer",
        "new": "Senior Software Engineer"
      }
    },
    "updatedBy": "user_xyz789",
    "reason": "Customer provided updated information"
  }
}
```

**Campos Chave**:

* `entity`: Objeto de entidade completo com dados atualizados
* `changes`: Objeto mostrando o que mudou (valores antigos vs novos)
* `updatedBy`: ID do usuário que atualizou a entidade
* `reason`: Motivo opcional para a atualização

### entity.status\_changed

```json theme={null}
{
  "event": "entity.status_changed",
  "timestamp": "2025-01-07T10:10:00.000Z",
  "payload": {
    "status": "active",
    "previousStatus": "under_review",
    "reason": "KYC verification completed successfully",
    "entity": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "customer_abc123",
      "name": "John Doe",
      "type": "person",
      "taxId": "123-45-6789",
      "countryCode": "US",
      "status": "active",
      "updatedAt": "2025-01-07T10:10:00.000Z"
    }
  }
}
```

**Campos Chave**:

* `status`: Novo status
* `previousStatus`: Status anterior
* `reason`: Por que o status mudou
* `entity`: Objeto de entidade completo

### entity.country\_activation\_changed

```json theme={null}
{
  "event": "entity.country_activation_changed",
  "timestamp": "2026-07-03T15:00:00.000Z",
  "organizationId": "org-uuid",
  "payload": {
    "entity": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "merchant_123",
      "name": "Acme Corp",
      "type": "company",
      "countryCode": "US"
    },
    "countryCode": "CO",
    "status": "activation_requested",
    "previousStatus": "deactivated",
    "activeCountryCodes": ["MX"],
    "countries": [
      { "countryCode": "AR", "status": "deactivated" },
      { "countryCode": "CO", "status": "activation_requested" },
      { "countryCode": "MX", "status": "activated" }
    ],
    "timeline": [
      {
        "previousStatus": "deactivated",
        "status": "activation_requested",
        "changedAt": "2026-07-03T15:00:00.000Z"
      }
    ],
    "changedAt": "2026-07-03T15:00:00.000Z"
  }
}
```

**Campos Chave**:

* `countryCode`: País cuja ativação mudou (AR, BR, CL, CO, MX, US)
* `status` / `previousStatus`: Status novo e anterior
* `activeCountryCodes`: Snapshot de países em `activated` após a alteração (ordem do allowlist)
* `countries`: Snapshot completo dos seis países do allowlist após a alteração
* `timeline`: Histórico cronológico de mudanças de status para esse país (da auditoria), cada item com `previousStatus`, `status` e `changedAt`
* `entity.externalId`: Identificador do merchant
* `changedAt`: Timestamp ISO da alteração

## Configuração de Filtros

### Filtrar por Tipo de Entidade

Receber apenas eventos para tipos de entidades específicos:

```json theme={null}
{
  "eventTypes": ["entity.created", "entity.updated"],
  "filters": {
    "entityTypes": ["person", "company"]
  }
}
```

Esta configuração acionará apenas webhooks para entidades de pessoa e empresa, ignorando dispositivos e outros tipos.

### Filtrar por Mudança de Status

Receber apenas eventos quando o status da entidade mudar para valores específicos:

```json theme={null}
{
  "eventTypes": ["entity.status_changed"],
  "filters": {
    "entityTypes": ["person"],
    "statusChanges": {
      "to": "blocked"
    }
  }
}
```

Isso acionará apenas quando uma entidade de pessoa for mudada **PARA** status `blocked`.

**Filtrar quando mudar DE um status específico**:

```json theme={null}
{
  "eventTypes": ["entity.status_changed"],
  "filters": {
    "statusChanges": {
      "from": "active",
      "to": "suspended"
    }
  }
}
```

Isso acionará apenas quando o status mudar de `active` para `suspended`.

## Exemplos de Código

### Node.js - Lidando com Eventos de Entidades

```javascript theme={null}
const express = require('express');
const app = express();

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

app.post('/webhooks/entity', async (req, res) => {
  try {
    // Verificar assinatura (veja guia de segurança)
    if (!verifySignature(req.rawBody, req.headers['x-webhook-signature'])) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const { event, payload } = req.body;

    // Rotear para o manipulador apropriado
    switch (event) {
      case 'entity.created':
        await handleEntityCreated(payload);
        break;

      case 'entity.updated':
        await handleEntityUpdated(payload);
        break;

      case 'entity.status_changed':
        await handleEntityStatusChanged(payload);
        break;

      default:
        console.warn('Unknown event type:', event);
    }

    res.status(200).json({ success: true });
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({ error: error.message });
  }
});

async function handleEntityCreated(payload) {
  const { entity, createdBy, metadata } = payload;

  console.log(`Entity created: ${entity.name} (${entity.type})`);

  // Sincronizar com seu banco de dados
  await db.entities.create({
    data: {
      gu1Id: entity.id,
      externalId: entity.externalId,
      name: entity.name,
      type: entity.type,
      status: entity.status,
      data: entity.entityData,
      attributes: entity.attributes,
      createdBy,
      metadata
    }
  });

  // Acionar fluxos de trabalho baseados no tipo de entidade
  if (entity.type === 'person') {
    await sendWelcomeEmail(entity.attributes.email);
  }
}

async function handleEntityUpdated(payload) {
  const { entity, changes, updatedBy, reason } = payload;

  console.log(`Entity updated: ${entity.name}`);
  console.log('Changes:', changes);

  // Atualizar seu banco de dados
  await db.entities.update({
    where: { gu1Id: entity.id },
    data: {
      name: entity.name,
      status: entity.status,
      data: entity.entityData,
      attributes: entity.attributes,
      lastUpdatedBy: updatedBy,
      updatedAt: new Date()
    }
  });

  // Registrar mudanças para trilha de auditoria
  await db.entityChanges.create({
    data: {
      entityId: entity.id,
      changes,
      changedBy: updatedBy,
      reason,
      timestamp: new Date()
    }
  });
}

async function handleEntityStatusChanged(payload) {
  const { entity, status, previousStatus, reason } = payload;

  console.log(`Entity status changed: ${previousStatus} → ${status}`);

  // Atualizar banco de dados
  await db.entities.update({
    where: { gu1Id: entity.id },
    data: {
      status,
      statusChangedAt: new Date()
    }
  });

  // Tomar ação baseada no novo status
  switch (status) {
    case 'active':
      await activateCustomerAccount(entity.externalId);
      await sendNotification(entity, 'Your account has been activated');
      break;

    case 'blocked':
      await deactivateCustomerAccount(entity.externalId);
      await sendNotification(entity, 'Your account has been blocked');
      break;

    case 'suspended':
      await suspendCustomerAccount(entity.externalId);
      await sendNotification(entity, 'Your account has been suspended');
      break;
  }

  // Registrar mudança de status
  await db.statusHistory.create({
    data: {
      entityId: entity.id,
      fromStatus: previousStatus,
      toStatus: status,
      reason,
      timestamp: new Date()
    }
  });
}

app.listen(3000);
```

## Melhores Práticas

<AccordionGroup>
  <Accordion title="Use externalId para Buscas">
    O `entity.externalId` é seu identificador único. Use-o para buscar entidades no seu banco de dados:

    ```javascript theme={null}
    const customer = await db.customers.findUnique({
      where: { externalId: entity.externalId }
    });
    ```
  </Accordion>

  <Accordion title="Armazene o ID da Entidade Gu1">
    Sempre armazene o ID da entidade Gu1 no seu banco de dados para referência:

    ```javascript theme={null}
    await db.customers.create({
      data: {
        externalId: entity.externalId,
        gu1Id: entity.id, // Armazenar isso
        name: entity.name
      }
    });
    ```
  </Accordion>

  <Accordion title="Lide com Todos os Tipos de Eventos">
    Mesmo se você se inscrever apenas em eventos específicos, lide com todos os tipos de eventos graciosamente:

    ```javascript theme={null}
    switch (event) {
      case 'entity.created':
        await handleEntityCreated(payload);
        break;

      default:
        console.warn('Unhandled event type:', event);
        // Ainda retornar 200
    }
    ```
  </Accordion>

  <Accordion title="Registre Mudanças para Auditoria">
    Mantenha uma trilha de auditoria de todas as mudanças de entidades:

    ```javascript theme={null}
    await db.auditLog.create({
      data: {
        entityId: entity.id,
        event,
        changes,
        timestamp: new Date(),
        webhook: req.body
      }
    });
    ```
  </Accordion>

  <Accordion title="Implemente Idempotência">
    Use o ID da entidade e timestamp para prevenir processamento duplicado:

    ```javascript theme={null}
    const webhookId = `${entity.id}_${event}_${timestamp}`;
    const processed = await db.webhookLog.findUnique({
      where: { webhookId }
    });

    if (processed) {
      return; // Pular duplicado
    }
    ```
  </Accordion>

  <Accordion title="Use Filtros para Reduzir Ruído">
    Configure filtros para receber apenas eventos relevantes:

    ```json theme={null}
    {
      "eventTypes": ["entity.status_changed"],
      "filters": {
        "entityTypes": ["person"],
        "statusChanges": {
          "to": "active"
        }
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Solução de Problemas

<AccordionGroup>
  <Accordion title="Não Recebo Eventos entity.created">
    **Verificar**:

    * Webhook está inscrito no evento `entity.created`
    * Tipo de entidade corresponde aos seus filtros (se configurados)
    * Webhook está habilitado no dashboard
    * Endpoint é publicamente acessível

    **Testar**:

    ```bash theme={null}
    # Criar uma entidade de teste
    curl -X POST https://api.gu1.io/entities \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -d '{"name":"Test","type":"person"}'
    ```
  </Accordion>

  <Accordion title="Faltam Mudanças em entity.updated">
    O objeto `changes` inclui apenas campos que realmente mudaram. Se você não vê um campo, significa que ele não foi atualizado.

    **Exemplo**:

    ```json theme={null}
    {
      "changes": {
        "name": {
          "old": "John",
          "new": "John Doe"
        }
      }
    }
    ```

    Apenas `name` mudou, outros campos permanecem os mesmos.
  </Accordion>

  <Accordion title="entity.status_changed Não Dispara">
    **Verificar**:

    * Status realmente mudou (não apenas entidade atualizada)
    * Filtros correspondem à mudança de status (from/to)
    * Mudança de status não está sendo filtrada

    **Exemplo de filtro que pode bloquear eventos**:

    ```json theme={null}
    {
      "filters": {
        "statusChanges": {
          "to": "blocked"
        }
      }
    }
    ```

    Isso disparará APENAS quando o status mudar PARA `blocked`.
  </Accordion>
</AccordionGroup>

## Próximos Passos

<CardGroup cols={2}>
  <Card title="Eventos KYC" icon="id-card" href="/pt/webhooks/events/kyc-events">
    Lidar com eventos de verificação KYC
  </Card>

  <Card title="Eventos de Regras" icon="gavel" href="/pt/webhooks/events/rule-events">
    Processar acionamentos de regras de conformidade
  </Card>

  <Card title="Segurança de Webhooks" icon="shield" href="/pt/webhooks/security">
    Proteger seus endpoints de webhook
  </Card>

  <Card title="Configuração" icon="gear" href="/pt/webhooks/configuration">
    Configurar ajustes de webhook
  </Card>
</CardGroup>
