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

> Receba notificações em tempo real quando o status de verificação KYC mudar — com eventos webhook gu1 para integração downstream em tempo real.

## Visão Geral

Os eventos de webhook KYC permitem que você receba notificações em tempo real quando o status de uma verificação KYC mudar. Gu1 envia automaticamente solicitações HTTP POST para seu endpoint de webhook configurado sempre que um status de validação é atualizado, permitindo que você automatize fluxos de trabalho de integração de clientes e mantenha a conformidade.

## Por Que Usar Webhooks KYC?

<CardGroup cols={2}>
  <Card title="Atualizações em Tempo Real" icon="bolt">
    Receba notificações instantâneas quando o status de verificação mudar
  </Card>

  <Card title="Eficiente" icon="gauge-high">
    Não é necessário consultar a API repetidamente
  </Card>

  <Card title="Fluxos de Trabalho Automatizados" icon="robot">
    Atualize automaticamente contas de usuário com base nos resultados de verificação
  </Card>

  <Card title="Melhor UX" icon="face-smile">
    Notifique clientes imediatamente após a verificação
  </Card>
</CardGroup>

## Eventos Disponíveis

Gu1 envia webhooks para os seguintes eventos de validação KYC:

| Tipo de Evento               | Descrição                   | Quando Acionado                                                          |
| ---------------------------- | --------------------------- | ------------------------------------------------------------------------ |
| `kyc.validation_created`     | Sessão de validação criada  | Quando você cria uma nova validação KYC                                  |
| `kyc.validation_in_progress` | Cliente iniciou verificação | Cliente está completando ativamente verificação (preenchendo formulário) |
| `kyc.validation_in_review`   | Verificação em revisão      | Verificação completada, requer revisão manual da equipe de compliance    |
| `kyc.validation_approved`    | Verificação aprovada        | Identidade verificada com sucesso                                        |
| `kyc.validation_rejected`    | Verificação rejeitada       | Verificação de identidade falhou                                         |
| `kyc.validation_abandoned`   | Cliente abandonou processo  | Cliente saiu sem completar                                               |
| `kyc.validation_expired`     | Sessão de validação expirou | Sessão expirou (tipicamente após 7 dias)                                 |
| `kyc.validation_cancelled`   | Validação cancelada         | Validação cancelada manualmente pela organização                         |

## Estrutura do Payload do Evento

Todos os webhooks KYC usam o **mesmo envelope externo**. O objeto `payload` é o **registro de validação KYC** armazenado no Gu1 (mesmos nomes de campo que no banco/API: `id` é o UUID da validação, além de `entityId`, `organizationId`, `status`, `decision`, `extractedData`, `verifiedFields`, `warnings`, `metadata`, timestamps, etc.).

<Note>
  O campo **`entity`** (linha completa da entidade no Gu1) é enviado **somente** em estados **finais**: `kyc.validation_approved` e `kyc.validation_rejected`. Não é incluído em `created`, `in_progress`, `in_review`, `abandoned`, `expired` ou `cancelled`. É um campo **adicional**: os demais campos do payload permanecem iguais. Em `kyc.validation_approved`, o preenchimento automático opcional da pessoa a partir do KYC continua **depois** do webhook (mesma ordem de antes); `payload.entity` é um instantâneo **no momento do envio** (antes desse passo). Para o estado após o preenchimento automático, use a API de entidades.
</Note>

```json theme={null}
{
  "event": "kyc.validation_approved",
  "timestamp": "2025-01-07T11:00:00Z",
  "organizationId": "org-uuid",
  "payload": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "entityId": "123e4567-e89b-12d3-a456-426614174000",
    "organizationId": "org-uuid",
    "status": "approved",
    "decision": {},
    "extractedData": {},
    "verifiedFields": [],
    "warnings": [],
    "entity": {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "externalId": "customer_xyz789",
      "name": "John Doe",
      "type": "person",
      "taxId": "…",
      "countryCode": "US",
      "status": "active",
      "entityData": {},
      "attributes": {}
    }
  }
}
```

Quando presente, `payload.entity` inclui **todas as colunas** da entidade (JSON seguro, datas em ISO).

### Campos comuns do envelope

<ResponseField name="event" type="string">
  O tipo de evento (por exemplo, `kyc.validation_approved`)
</ResponseField>

<ResponseField name="timestamp" type="string">
  Timestamp ISO 8601 quando o evento ocorreu
</ResponseField>

<ResponseField name="organizationId" type="string">
  Seu ID de organização
</ResponseField>

<ResponseField name="payload.id" type="string">
  ID da validação KYC no Gu1 (chave primária do registro)
</ResponseField>

<ResponseField name="payload.entityId" type="string">
  O ID da entidade (pessoa) sendo verificada
</ResponseField>

<ResponseField name="payload.entity" type="object">
  Apenas em `kyc.validation_approved` e `kyc.validation_rejected`. Instantâneo da linha da entidade no Gu1 no momento do envio (na aprovação, antes do preenchimento automático opcional).
</ResponseField>

<ResponseField name="payload.status" type="string">
  Status atual de validação: `pending`, `in_progress`, `in_review`, `approved`, `rejected`, `abandoned`, `expired`, `cancelled`
</ResponseField>

### Objeto `decision` (`payload.decision`)

Quando uma validação atinge um estado terminal ou `in_review` com resultados do provedor, `payload.decision` contém o resultado completo do fluxo KYC. O Gu1 **sempre persiste e devolve ambas as formas** por feature: objeto singular (legacy) **e** array de um elemento (atual). Você pode ler `id_verification` ou `id_verifications[0]`; eles ficam sincronizados. O mesmo vale para `liveness` / `liveness_checks`, `face_match` / `face_matches`, `aml_screening` / `aml_screenings` e `ip_analysis` / `ip_analyses`.

Campos de mídia (`front_image`, `reference_image`, `images.*`, etc.) são **chaves de armazenamento Gu1** (`kyc/...`) após o ingest. Obtenha-as via a [API de mídia de validação](/pt/use-cases/kyc/validation-media). Registros antigos podem ainda ter URLs HTTPS de curta duração até sincronizar.

**Exemplo aprovado (completo):**

```json theme={null}
{
  "status": "Approved",
  "workflow_type": "standard",
  "session_id": "7c0fa22d-0cfa-4a78-8090-7c842397e788",
  "session_number": 921,
  "features": ["ID_VERIFICATION", "LIVENESS", "FACE_MATCH", "IP_ANALYSIS"],
  "images": {
    "documentFront": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
    "documentBack": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-back.jpg",
    "selfie": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg"
  },
  "id_verification": {
    "status": "Approved",
    "node_id": "feature_ocr",
    "document_type": "Passport",
    "document_number": "AB123456",
    "first_name": "John",
    "last_name": "Doe",
    "full_name": "John Doe",
    "date_of_birth": "1990-05-20",
    "nationality": "US",
    "gender": "M",
    "age": 35,
    "issuing_state": "US",
    "expiration_date": "2030-05-20",
    "date_of_issue": "2020-05-20",
    "front_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
    "back_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-back.jpg",
    "portrait_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
    "warnings": [],
    "matches": []
  },
  "id_verifications": [
    {
      "status": "Approved",
      "node_id": "feature_ocr",
      "document_type": "Passport",
      "document_number": "AB123456",
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "John Doe",
      "date_of_birth": "1990-05-20",
      "nationality": "US",
      "gender": "M",
      "age": 35,
      "issuing_state": "US",
      "expiration_date": "2030-05-20",
      "date_of_issue": "2020-05-20",
      "front_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
      "back_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-back.jpg",
      "portrait_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
      "warnings": [],
      "matches": []
    }
  ],
  "liveness": {
    "status": "Approved",
    "node_id": "feature_liveness",
    "score": 98,
    "method": "PASSIVE",
    "reference_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-reference.jpg",
    "video_url": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-video.webm",
    "face_quality": 92.5,
    "warnings": [],
    "matches": []
  },
  "liveness_checks": [
    {
      "status": "Approved",
      "node_id": "feature_liveness",
      "score": 98,
      "method": "PASSIVE",
      "reference_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-reference.jpg",
      "video_url": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-video.webm",
      "face_quality": 92.5,
      "warnings": [],
      "matches": []
    }
  ],
  "face_match": {
    "status": "Approved",
    "node_id": "feature_face_match",
    "score": 95,
    "source_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
    "target_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
    "warnings": []
  },
  "face_matches": [
    {
      "status": "Approved",
      "node_id": "feature_face_match",
      "score": 95,
      "source_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
      "target_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
      "warnings": []
    }
  ],
  "aml_screening": {
    "status": "Approved",
    "node_id": "feature_aml",
    "warnings": []
  },
  "aml_screenings": [
    {
      "status": "Approved",
      "node_id": "feature_aml",
      "warnings": []
    }
  ],
  "ip_analysis": {
    "status": "Approved",
    "node_id": "feature_ip_analysis",
    "ip_address": "203.0.113.10",
    "country": "US",
    "region": "New York",
    "city": "New York",
    "is_vpn": false,
    "is_proxy": false,
    "warnings": []
  },
  "ip_analyses": [
    {
      "status": "Approved",
      "node_id": "feature_ip_analysis",
      "ip_address": "203.0.113.10",
      "country": "US",
      "region": "New York",
      "city": "New York",
      "is_vpn": false,
      "is_proxy": false,
      "warnings": []
    }
  ]
}
```

**Exemplo rejeitado (completo):**

```json theme={null}
{
  "status": "Declined",
  "workflow_type": "standard",
  "session_id": "7c0fa22d-0cfa-4a78-8090-7c842397e788",
  "session_number": 921,
  "features": ["ID_VERIFICATION", "LIVENESS", "FACE_MATCH"],
  "images": {
    "documentFront": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
    "selfie": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg"
  },
  "id_verification": {
    "status": "Declined",
    "node_id": "feature_ocr",
    "document_type": "Passport",
    "document_number": "AB123456",
    "first_name": "John",
    "last_name": "Doe",
    "full_name": "John Doe",
    "front_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
    "portrait_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
    "warnings": [
      {
        "risk": "DOCUMENT_AUTHENTICITY_FAILED",
        "feature": "ID_VERIFICATION",
        "short_description": "Document authenticity could not be verified",
        "long_description": "The document failed authenticity checks.",
        "log_type": "error"
      }
    ],
    "matches": []
  },
  "id_verifications": [
    {
      "status": "Declined",
      "node_id": "feature_ocr",
      "document_type": "Passport",
      "document_number": "AB123456",
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "John Doe",
      "front_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
      "portrait_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
      "warnings": [
        {
          "risk": "DOCUMENT_AUTHENTICITY_FAILED",
          "feature": "ID_VERIFICATION",
          "short_description": "Document authenticity could not be verified",
          "long_description": "The document failed authenticity checks.",
          "log_type": "error"
        }
      ],
      "matches": []
    }
  ],
  "liveness": {
    "status": "Declined",
    "node_id": "feature_liveness",
    "score": 42,
    "method": "PASSIVE",
    "reference_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-reference.jpg",
    "warnings": [
      {
        "risk": "LIVENESS_FAILED",
        "feature": "LIVENESS",
        "short_description": "Liveness detection failed",
        "long_description": "The liveness check did not pass the required threshold.",
        "log_type": "error"
      }
    ],
    "matches": []
  },
  "liveness_checks": [
    {
      "status": "Declined",
      "node_id": "feature_liveness",
      "score": 42,
      "method": "PASSIVE",
      "reference_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-reference.jpg",
      "warnings": [
        {
          "risk": "LIVENESS_FAILED",
          "feature": "LIVENESS",
          "short_description": "Liveness detection failed",
          "long_description": "The liveness check did not pass the required threshold.",
          "log_type": "error"
        }
      ],
      "matches": []
    }
  ],
  "face_match": {
    "status": "Declined",
    "node_id": "feature_face_match",
    "score": 38,
    "source_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
    "target_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
    "warnings": [
      {
        "risk": "FACE_MATCH_LOW_CONFIDENCE",
        "feature": "FACE_MATCH",
        "short_description": "Face match confidence below threshold",
        "long_description": "The selfie did not match the document portrait with sufficient confidence.",
        "log_type": "error"
      }
    ]
  },
  "face_matches": [
    {
      "status": "Declined",
      "node_id": "feature_face_match",
      "score": 38,
      "source_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
      "target_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
      "warnings": [
        {
          "risk": "FACE_MATCH_LOW_CONFIDENCE",
          "feature": "FACE_MATCH",
          "short_description": "Face match confidence below threshold",
          "long_description": "The selfie did not match the document portrait with sufficient confidence.",
          "log_type": "error"
        }
      ]
    }
  ]
}
```

## Payloads Específicos de Eventos

### kyc.validation\_created

Enviado quando uma nova validação KYC é criada. **`entity` não é incluído** (evento não terminal).

```json theme={null}
{
  "event": "kyc.validation_created",
  "timestamp": "2025-01-07T10:30:00Z",
  "organizationId": "org-123",
  "payload": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "entityId": "123e4567-e89b-12d3-a456-426614174000",
    "status": "pending",
    "providerSessionUrl": "https://kyc.gu1.io/validate/abc123",
    "expiresAt": "2025-01-14T10:30:00Z"
  }
}
```

**Caso de uso**: Envie a URL de validação para seu cliente via email ou SMS.

### kyc.validation\_in\_review

Enviado quando um cliente completa a verificação e requer revisão manual da equipe de compliance. **`entity` não é incluído.**

```json theme={null}
{
  "event": "kyc.validation_in_review",
  "timestamp": "2025-01-07T10:50:00Z",
  "organizationId": "org-123",
  "payload": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "entityId": "123e4567-e89b-12d3-a456-426614174000",
    "status": "in_review"
  }
}
```

**Caso de uso**: Notifique a equipe de compliance para revisão manual. Atualize a UI para mostrar "Em revisão pela equipe de compliance".

### kyc.validation\_approved

Enviado quando a verificação é concluída com sucesso. **`entity` é incluído** (linha completa no momento do envio; o preenchimento automático opcional pode rodar depois, como antes).

```json theme={null}
{
  "event": "kyc.validation_approved",
  "timestamp": "2025-01-07T11:00:00Z",
  "organizationId": "org-123",
  "payload": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "entityId": "123e4567-e89b-12d3-a456-426614174000",
    "entity": {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "externalId": "customer_xyz789",
      "name": "John Doe",
      "type": "person",
      "entityData": { "person": { "firstName": "John", "lastName": "Doe" } },
      "attributes": {}
    },
    "status": "approved",
    "verifiedAt": "2025-01-07T11:00:00Z",
    "extractedData": {
      "firstName": "John",
      "lastName": "Doe",
      "dateOfBirth": "1990-05-20",
      "nationality": "US",
      "documentNumber": "AB123456",
      "documentType": "passport",
      "documentExpiry": "2030-05-20"
    },
    "verifiedFields": [
      "firstName",
      "lastName",
      "dateOfBirth",
      "nationality",
      "documentNumber"
    ],
    "warnings": [],
    "decision": {
      "status": "Approved",
      "workflow_type": "standard",
      "session_id": "7c0fa22d-0cfa-4a78-8090-7c842397e788",
      "session_number": 921,
      "features": ["ID_VERIFICATION", "LIVENESS", "FACE_MATCH", "IP_ANALYSIS"],
      "images": {
        "documentFront": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
        "documentBack": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-back.jpg",
        "selfie": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg"
      },
      "id_verification": {
        "status": "Approved",
        "node_id": "feature_ocr",
        "document_type": "Passport",
        "document_number": "AB123456",
        "first_name": "John",
        "last_name": "Doe",
        "full_name": "John Doe",
        "date_of_birth": "1990-05-20",
        "nationality": "US",
        "gender": "M",
        "age": 35,
        "issuing_state": "US",
        "expiration_date": "2030-05-20",
        "date_of_issue": "2020-05-20",
        "front_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
        "back_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-back.jpg",
        "portrait_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
        "warnings": [],
        "matches": []
      },
      "id_verifications": [
        {
          "status": "Approved",
          "node_id": "feature_ocr",
          "document_type": "Passport",
          "document_number": "AB123456",
          "first_name": "John",
          "last_name": "Doe",
          "full_name": "John Doe",
          "date_of_birth": "1990-05-20",
          "nationality": "US",
          "gender": "M",
          "age": 35,
          "issuing_state": "US",
          "expiration_date": "2030-05-20",
          "date_of_issue": "2020-05-20",
          "front_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
          "back_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-back.jpg",
          "portrait_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
          "warnings": [],
          "matches": []
        }
      ],
      "liveness": {
        "status": "Approved",
        "node_id": "feature_liveness",
        "score": 98,
        "method": "PASSIVE",
        "reference_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-reference.jpg",
        "video_url": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-video.webm",
        "face_quality": 92.5,
        "warnings": [],
        "matches": []
      },
      "liveness_checks": [
        {
          "status": "Approved",
          "node_id": "feature_liveness",
          "score": 98,
          "method": "PASSIVE",
          "reference_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-reference.jpg",
          "video_url": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/liveness-video.webm",
          "face_quality": 92.5,
          "warnings": [],
          "matches": []
        }
      ],
      "face_match": {
        "status": "Approved",
        "node_id": "feature_face_match",
        "score": 95,
        "source_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
        "target_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
        "warnings": []
      },
      "face_matches": [
        {
          "status": "Approved",
          "node_id": "feature_face_match",
          "score": 95,
          "source_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/document-front.jpg",
          "target_image": "kyc/global_gueno_validation_kyc/org-uuid/entity-uuid/validation-uuid/selfie.jpg",
          "warnings": []
        }
      ],
      "aml_screening": {
        "status": "Approved",
        "node_id": "feature_aml",
        "warnings": []
      },
      "aml_screenings": [
        {
          "status": "Approved",
          "node_id": "feature_aml",
          "warnings": []
        }
      ],
      "ip_analysis": {
        "status": "Approved",
        "node_id": "feature_ip_analysis",
        "ip_address": "203.0.113.10",
        "country": "US",
        "region": "New York",
        "city": "New York",
        "is_vpn": false,
        "is_proxy": false,
        "warnings": []
      },
      "ip_analyses": [
        {
          "status": "Approved",
          "node_id": "feature_ip_analysis",
          "ip_address": "203.0.113.10",
          "country": "US",
          "region": "New York",
          "city": "New York",
          "is_vpn": false,
          "is_proxy": false,
          "warnings": []
        }
      ]
    }
  }
}
```

**Campos adicionais**: `entity` contém todas as colunas da entidade no Gu1 no momento do envio.

**Caso de uso**: Ative a conta do cliente e conceda acesso aos serviços.

### kyc.validation\_rejected

Enviado quando a verificação falha. **`entity` é incluído.**

```json theme={null}
{
  "event": "kyc.validation_rejected",
  "timestamp": "2025-01-07T11:00:00Z",
  "organizationId": "org-123",
  "payload": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "entityId": "123e4567-e89b-12d3-a456-426614174000",
    "entity": {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "externalId": "customer_xyz789",
      "name": "John Doe",
      "type": "person",
      "entityData": {},
      "attributes": {}
    },
    "status": "rejected",
    "verifiedAt": "2025-01-07T11:00:00Z",
    "warnings": [
      "Document authenticity check failed",
      "Face match confidence low",
      "Liveness detection failed"
    ],
    "rejectionReason": "Document authenticity could not be verified"
  }
}
```

**Caso de uso**: Notifique o cliente que a verificação falhou e forneça orientação sobre os próximos passos.

### kyc.validation\_cancelled

Enviado quando uma validação é cancelada manualmente pela organização. **`entity` não é incluído.**

```json theme={null}
{
  "event": "kyc.validation_cancelled",
  "timestamp": "2025-01-07T12:00:00Z",
  "organizationId": "org-123",
  "payload": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "entityId": "123e4567-e89b-12d3-a456-426614174000",
    "status": "cancelled",
    "cancelledAt": "2025-01-07T12:00:00Z",
    "cancelledBy": "user_123"
  }
}
```

**Caso de uso**: Notifique o cliente que a validação foi cancelada. Limpe recursos associados e atualize o status no seu sistema.

## Exemplos de Código

### Node.js - Lidando com Eventos KYC

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

const app = express();

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

app.post('/webhooks/kyc', async (req, res) => {
  try {
    // Verificar assinatura de webhook (veja guia de segurança)
    const signature = req.headers['x-webhook-signature'];
    const webhookSecret = process.env.GU1_WEBHOOK_SECRET;

    if (!verifySignature(req.rawBody, signature, webhookSecret)) {
      console.error('Invalid webhook signature');
      return res.status(401).json({ error: 'Invalid signature' });
    }

    // Extrair dados do webhook
    const { event, timestamp, organizationId, payload } = req.body;

    console.log('Received KYC webhook:', {
      event,
      validationId: payload.id,
      status: payload.status
    });

    // Processar o webhook com base no tipo de evento
    await handleKycWebhook(event, payload);

    // Retornar 200 para confirmar recebimento
    res.status(200).json({
      success: true,
      message: 'Webhook received'
    });
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({
      error: error.message
    });
  }
});

async function handleKycWebhook(event, data) {
  const { validationId, entityId, entity, status } = data;

  // Atualizar seu banco de dados com ID de validação do Gu1
  await db.updateEntity(entity.externalId, {
    kycValidationId: validationId,
    kycStatus: status,
    lastUpdated: new Date()
  });

  // Realizar ações com base no tipo de evento
  switch (event) {
    case 'kyc.validation_created':
      console.log('KYC validation created for:', entity.name);
      // Enviar URL de validação ao cliente
      await sendValidationEmail(entity, data.validationUrl);
      break;

    case 'kyc.validation_in_progress':
      await notifyCustomer(entity.externalId, 'verification-started');
      break;

    case 'kyc.validation_in_review':
      // Notificar equipe de compliance
      await notifyComplianceTeam(entity, validationId);
      await notifyCustomer(entity.externalId, 'verification-in-review');
      break;

    case 'kyc.validation_approved':
      // Extrair dados verificados
      const { extractedData, verifiedFields } = data;

      await db.updateEntity(entity.externalId, {
        verifiedData: extractedData,
        verifiedFields: verifiedFields,
        verifiedAt: data.verifiedAt,
        isVerified: true
      });

      // Ativar conta de cliente
      await activateCustomerAccount(entity.externalId);
      await notifyCustomer(entity.externalId, 'verification-approved');
      break;

    case 'kyc.validation_rejected':
      await db.updateEntity(entity.externalId, {
        isVerified: false,
        rejectionReasons: data.warnings,
        rejectionReason: data.rejectionReason
      });

      await notifyCustomer(entity.externalId, 'verification-rejected', {
        reasons: data.warnings
      });
      break;

    case 'kyc.validation_abandoned':
      await notifyCustomer(entity.externalId, 'verification-incomplete', {
        lastStep: data.lastStep
      });
      break;

    case 'kyc.validation_expired':
      await notifyCustomer(entity.externalId, 'verification-expired');
      // Limpar validação expirada
      await db.deleteValidation(validationId);
      break;

    case 'kyc.validation_cancelled':
      await notifyCustomer(entity.externalId, 'verification-cancelled');
      // Limpar recursos associados
      await db.deleteValidation(validationId);
      break;
  }
}

function verifySignature(rawBody, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  return signature === expectedSignature;
}

app.listen(3000);
```

## Melhores Práticas

<AccordionGroup>
  <Accordion title="Use entity.externalId para Busca">
    O webhook inclui `entity.externalId` que é o ID que você forneceu ao criar a entidade. Use-o para buscar o cliente no seu banco de dados.

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

  <Accordion title="Armazene IDs de Validação">
    Armazene o `validationId` do Gu1 no seu banco de dados. Isso permite que você consulte detalhes de validação mais tarde, se necessário.

    ```javascript theme={null}
    await db.updateCustomer(customer.id, {
      kycValidationId: data.validationId,
      kycStatus: data.status
    });
    ```
  </Accordion>

  <Accordion title="Lide com Idempotência">
    Você pode receber o mesmo webhook múltiplas vezes. Use o `validationId` para garantir que você processe cada evento apenas uma vez.

    ```javascript theme={null}
    async function handleWebhook(webhook) {
      const alreadyProcessed = await db.checkWebhookProcessed(
        webhook.payload.validationId,
        webhook.event
      );

      if (alreadyProcessed) {
        return; // Pular duplicado
      }

      // Processar webhook
      await processValidation(webhook.payload);

      // Marcar como processado
      await db.markWebhookProcessed(
        webhook.payload.validationId,
        webhook.event
      );
    }
    ```
  </Accordion>

  <Accordion title="Retorne 200 Rapidamente">
    Sempre retorne um código de status 200 o mais rápido possível para confirmar o recebimento. Processe o webhook assincronamente se necessário.

    ```javascript theme={null}
    app.post('/webhooks/kyc', async (req, res) => {
      // Confirmar imediatamente
      res.status(200).send('OK');

      // Processar assincronamente
      processWebhook(req.body).catch(console.error);
    });
    ```
  </Accordion>

  <Accordion title="Verifique Assinaturas">
    Sempre verifique o header `X-Webhook-Signature` para garantir que o webhook seja autêntico. Veja o [guia de segurança](/pt/webhooks/security) para detalhes.

    ```javascript theme={null}
    const signature = req.headers['x-webhook-signature'];
    if (!verifySignature(req.rawBody, signature, secret)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }
    ```
  </Accordion>
</AccordionGroup>

## Solução de Problemas

<AccordionGroup>
  <Accordion title="Não Recebo Webhooks">
    **Verificar estes itens:**

    * URL do webhook é publicamente acessível via HTTPS
    * Webhook está configurado e **habilitado** no dashboard
    * Inscrito nos tipos de eventos KYC corretos
    * Endpoint retorna código de status 200 dentro de 30 segundos
    * Verificar logs do servidor para solicitações recebidas
  </Accordion>

  <Accordion title="Falta extractedData">
    `extractedData` e `verifiedFields` são incluídos apenas em:

    * `kyc.validation_approved`
    * `kyc.validation_rejected`

    Eles não estão presentes em outros tipos de eventos como `validation_created` ou `validation_in_progress`.
  </Accordion>

  <Accordion title="Verificação de Assinatura Falhando">
    **Causas comuns:**

    * Usar secret errado (verificar dashboard para secret atual)
    * Verificar assinatura em JSON analisado em vez de corpo raw
    * Re-verificar a partir do JSON do **monitor de webhooks** (pretty-print / `JSON.stringify` ≠ bytes assinados)
    * Middleware que altera o body antes de verificar (alguns payloads falham, outros passam)
    * Secret não salvo corretamente após criação do webhook
    * Problemas de codificação (garantir UTF-8)

    Veja [Segurança de webhooks](/pt/webhooks/security) — especialmente *Corpo raw vs JSON analisado*, *Histórico no dashboard* e *Falhas intermitentes de assinatura*.
  </Accordion>

  <Accordion title="Recebendo Webhooks Duplicados">
    Este é um comportamento normal. Webhooks podem ser enviados múltiplas vezes devido a problemas de rede, timeouts ou tentativas.

    **Sempre implemente idempotência** usando o `validationId` do webhook e tipo de `event`.
  </Accordion>
</AccordionGroup>

## Próximos Passos

<CardGroup cols={2}>
  <Card title="Eventos de Entidades" icon="user" href="/pt/webhooks/events/entity-events">
    Lidar com eventos de ciclo de vida de entidades
  </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>
