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

> Reciba notificaciones en tiempo real cuando cambia el estado de verificación KYC — con eventos webhook de gu1 para integración downstream en tiempo real.

## Descripción General

Los eventos de webhook KYC le permiten recibir notificaciones en tiempo real cuando cambia el estado de una verificación KYC. Gu1 envía automáticamente solicitudes HTTP POST a su endpoint de webhook configurado cada vez que se actualiza un estado de validación, permitiéndole automatizar flujos de trabajo de incorporación de clientes y mantener el cumplimiento.

## ¿Por Qué Usar Webhooks KYC?

<CardGroup cols={2}>
  <Card title="Actualizaciones en Tiempo Real" icon="bolt">
    Reciba notificaciones instantáneas cuando cambie el estado de verificación
  </Card>

  <Card title="Eficiente" icon="gauge-high">
    No es necesario consultar la API repetidamente
  </Card>

  <Card title="Flujos de Trabajo Automatizados" icon="robot">
    Actualice automáticamente cuentas de usuario según resultados de verificación
  </Card>

  <Card title="Mejor UX" icon="face-smile">
    Notifique a los clientes inmediatamente después de la verificación
  </Card>
</CardGroup>

## Eventos Disponibles

Gu1 envía webhooks para los siguientes eventos de validación KYC:

| Tipo de Evento               | Descripción                  | Cuándo se Activa                                                              |
| ---------------------------- | ---------------------------- | ----------------------------------------------------------------------------- |
| `kyc.validation_created`     | Sesión de validación creada  | Cuando crea una nueva validación KYC                                          |
| `kyc.validation_in_progress` | Cliente comenzó verificación | El cliente está completando activamente la verificación (llenando formulario) |
| `kyc.validation_in_review`   | Verificación en revisión     | Verificación completada, requiere revisión manual del equipo de compliance    |
| `kyc.validation_approved`    | Verificación aprobada        | Identidad verificada con éxito                                                |
| `kyc.validation_rejected`    | Verificación rechazada       | Falló la verificación de identidad                                            |
| `kyc.validation_abandoned`   | Cliente abandonó proceso     | El cliente se fue sin completar                                               |
| `kyc.validation_expired`     | Sesión de validación expiró  | Sesión expirada (típicamente después de 7 días)                               |
| `kyc.validation_cancelled`   | Validación cancelada         | Validación cancelada manualmente por la organización                          |

## Estructura del Payload de Evento

Todos los webhooks KYC comparten el **mismo sobre exterior**. El objeto `payload` es el **registro de validación KYC** tal como está en Gu1 (mismos nombres de campo que en base de datos/API: `id` es el UUID de la validación, más `entityId`, `organizationId`, `status`, `decision`, `extractedData`, `verifiedFields`, `warnings`, `metadata`, marcas de tiempo, etc.).

<Note>
  El campo **`entity`** (fila completa de la entidad en Gu1) solo se envía en resultados **finales**: `kyc.validation_approved` y `kyc.validation_rejected`. No se incluye en `created`, `in_progress`, `in_review`, `abandoned`, `expired` ni `cancelled`. Es un campo **adicional**: el resto del payload no cambia. En `kyc.validation_approved`, el auto‑relleno opcional de persona desde KYC sigue ejecutándose **después** del webhook (mismo orden que antes); `payload.entity` es una instantánea **en el momento del envío** (antes de ese paso). Para el estado tras el auto‑relleno, consultá la 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": {}
    }
  }
}
```

Si está presente, `payload.entity` incluye **todas las columnas** de la entidad (JSON seguro, fechas en ISO).

### Campos comunes del sobre (envelope)

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

<ResponseField name="timestamp" type="string">
  Timestamp ISO 8601 cuando ocurrió el evento
</ResponseField>

<ResponseField name="organizationId" type="string">
  Su ID de organización
</ResponseField>

<ResponseField name="payload.id" type="string">
  ID de la validación KYC en Gu1 (clave primaria del registro)
</ResponseField>

<ResponseField name="payload.entityId" type="string">
  El ID de entidad (persona) siendo verificada
</ResponseField>

<ResponseField name="payload.entity" type="object">
  Solo en `kyc.validation_approved` y `kyc.validation_rejected`. Instantánea de la fila de entidad en Gu1 en el momento del envío (en aprobación, antes del auto‑relleno opcional).
</ResponseField>

<ResponseField name="payload.status" type="string">
  Estado actual de validación: `pending`, `in_progress`, `in_review`, `approved`, `rejected`, `abandoned`, `expired`, `cancelled`
</ResponseField>

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

Cuando una validación llega a un estado terminal o `in_review` con resultados del proveedor, `payload.decision` contiene el resultado completo del flujo KYC. Gu1 **siempre persiste y devuelve ambas formas** por cada feature: objeto singular (legacy) **y** array de un elemento (actual). Podés leer `id_verification` o `id_verifications[0]`; se mantienen sincronizados. Lo mismo aplica a `liveness` / `liveness_checks`, `face_match` / `face_matches`, `aml_screening` / `aml_screenings` e `ip_analysis` / `ip_analyses`.

Los campos de media (`front_image`, `reference_image`, `images.*`, etc.) son **claves de almacenamiento Gu1** (`kyc/...`) tras el ingest. Obtenelas vía la [API de media de validación](/es/use-cases/kyc/validation-media). Filas antiguas pueden tener URLs HTTPS de corta duración hasta sincronizar.

**Ejemplo aprobado (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": []
    }
  ]
}
```

**Ejemplo rechazado (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 cuando se crea una nueva validación KYC. El `payload` es el registro de validación; **`entity` no se incluye** (evento no 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**: Envíe la URL de validación a su cliente por correo electrónico o SMS.

### kyc.validation\_in\_progress

Enviado cuando un cliente inicia el proceso de verificación. **`entity` no se incluye.**

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

**Caso de uso**: Actualice la UI para mostrar estado "Verificación en progreso".

### kyc.validation\_in\_review

Enviado cuando un cliente completa la verificación y requiere revisión manual del equipo de compliance. **`entity` no se incluye.**

```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 al equipo de compliance para revisión manual. Actualice la UI para mostrar "En revisión por equipo de compliance".

### kyc.validation\_approved

Enviado cuando la verificación se completa con éxito. **`entity` se incluye** (fila completa en el momento del envío; el auto‑relleno opcional puede ejecutarse después, igual que 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",
      "address": {
        "street": "123 Main St",
        "city": "New York",
        "state": "NY",
        "postalCode": "10001",
        "country": "US"
      }
    },
    "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 Adicionales**:

* `entity`: Registro completo de la entidad en Gu1 al momento del envío (todas las columnas; fechas en ISO)
* `verifiedAt`: Timestamp cuando se aprobó la verificación
* `extractedData`: Información personal extraída del documento
* `verifiedFields`: Array de campos que se verificaron con éxito
* `warnings`: Array de advertencias detectadas durante la verificación (vacío si fue aprobado)
* `decision`: Resultados de verificación para cada verificación

**Caso de uso**: Active la cuenta del cliente y otorgue acceso a servicios.

### kyc.validation\_rejected

Enviado cuando falla la verificación. **`entity` se incluye** (estado actual de la entidad en Gu1).

```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",
    "extractedData": {},
    "verifiedFields": [],
    "warnings": [
      "Document authenticity check failed",
      "Face match confidence low",
      "Liveness detection failed"
    ],
    "decision": {
      "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"
            }
          ]
        }
      ]
    },
    "rejectionReason": "Document authenticity could not be verified"
  }
}
```

**Campos Adicionales**:

* `entity`: Registro completo de la entidad en Gu1 al momento del envío
* `warnings`: Array de razones por las que falló la verificación
* `rejectionReason`: Razón principal del rechazo

**Caso de uso**: Notifique al cliente que la verificación falló y proporcione orientación sobre los próximos pasos.

### kyc.validation\_abandoned

Enviado cuando un cliente inicia pero no completa la verificación. **`entity` no se incluye.**

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

**Campos Adicionales**:

* `lastStep`: El último paso que el cliente completó antes de abandonar

**Caso de uso**: Envíe un correo de recordatorio para completar la verificación.

### kyc.validation\_expired

Enviado cuando una sesión de validación expira sin completarse. **`entity` no se incluye.**

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

**Caso de uso**: Limpie validaciones pendientes y notifique al cliente para reiniciar la verificación.

### kyc.validation\_cancelled

Enviado cuando una validación es cancelada manualmente por la organización. **`entity` no se incluye.**

```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 al cliente que la validación fue cancelada. Limpie recursos asociados y actualice el estado en su sistema.

## Ejemplos de Código

### Node.js - Manejo de 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 firma de webhook (ver guía de seguridad)
    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' });
    }

    // Extraer datos del webhook
    const { event, timestamp, organizationId, payload } = req.body;

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

    // Procesar el webhook según tipo de evento
    await handleKycWebhook(event, payload);

    // Devolver 200 para confirmar recepción
    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;

  // Actualizar su base de datos con ID de validación de Gu1
  await db.updateEntity(entity.externalId, {
    kycValidationId: validationId,
    kycStatus: status,
    lastUpdated: new Date()
  });

  // Realizar acciones según tipo de evento
  switch (event) {
    case 'kyc.validation_created':
      console.log('KYC validation created for:', entity.name);
      // Enviar URL de validación al 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 al equipo de compliance
      await notifyComplianceTeam(entity, validationId);
      await notifyCustomer(entity.externalId, 'verification-in-review');
      break;

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

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

      // Activar cuenta 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');
      // Limpiar validación expirada
      await db.deleteValidation(validationId);
      break;

    case 'kyc.validation_cancelled':
      await notifyCustomer(entity.externalId, 'verification-cancelled');
      // Limpiar recursos asociados
      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);
```

### Python - Manejo de Eventos KYC

```python theme={null}
from flask import Flask, request, jsonify
import hmac
import hashlib
import logging

app = Flask(__name__)

@app.route('/webhooks/kyc', methods=['POST'])
def kyc_webhook():
    try:
        # Obtener firma del header
        signature = request.headers.get('X-Webhook-Signature')
        webhook_secret = os.getenv('GU1_WEBHOOK_SECRET')

        # Obtener cuerpo raw para verificación de firma
        raw_body = request.get_data(as_text=True)

        # Verificar firma
        if not verify_signature(raw_body, signature, webhook_secret):
            logging.error('Invalid webhook signature')
            return jsonify({'error': 'Invalid signature'}), 401

        # Analizar payload
        payload = request.json
        event = payload.get('event')
        data = payload.get('payload')

        logging.info(f'Received KYC webhook: {event}')

        # Procesar el webhook
        handle_kyc_webhook(event, data)

        return jsonify({
            'success': True,
            'message': 'Webhook received'
        }), 200

    except Exception as e:
        logging.error(f'Webhook error: {e}')
        return jsonify({
            'error': str(e)
        }), 500

def verify_signature(raw_body, signature, secret):
    """Verificar firma HMAC SHA-256"""
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        raw_body.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected_signature)

def handle_kyc_webhook(event, data):
    validation_id = data['validationId']
    entity_id = data['entityId']
    entity = data['entity']
    status = data['status']

    # Actualizar base de datos
    db.update_entity(
        external_id=entity['externalId'],
        kyc_validation_id=validation_id,
        kyc_status=status,
        last_updated=datetime.now()
    )

    # Manejar diferentes eventos
    if event == 'kyc.validation_created':
        send_validation_email(entity, data['validationUrl'])

    elif event == 'kyc.validation_in_review':
        notify_compliance_team(entity, validation_id)
        notify_customer(entity['externalId'], 'verification-in-review')

    elif event == 'kyc.validation_approved':
        db.update_entity(
            external_id=entity['externalId'],
            verified_data=data.get('extractedData'),
            verified_fields=data.get('verifiedFields'),
            verified_at=data.get('verifiedAt'),
            is_verified=True
        )
        activate_customer_account(entity['externalId'])
        notify_customer(entity['externalId'], 'verification-approved')

    elif event == 'kyc.validation_rejected':
        db.update_entity(
            external_id=entity['externalId'],
            is_verified=False,
            rejection_reasons=data.get('warnings')
        )
        notify_customer(entity['externalId'], 'verification-rejected')

    elif event == 'kyc.validation_abandoned':
        notify_customer(entity['externalId'], 'verification-incomplete')

    elif event == 'kyc.validation_expired':
        notify_customer(entity['externalId'], 'verification-expired')
        db.delete_validation(validation_id)

    elif event == 'kyc.validation_cancelled':
        notify_customer(entity['externalId'], 'verification-cancelled')
        db.delete_validation(validation_id)

if __name__ == '__main__':
    app.run(port=3000)
```

## Casos de Uso

### Caso de Uso 1: Activación Automática de Cuenta

Active automáticamente cuentas de clientes cuando se aprueba el KYC:

```javascript theme={null}
async function handleKycApproved(data) {
  const { entity, extractedData, verifiedFields } = data;

  // Actualizar registro de cliente
  await db.customers.update({
    where: { externalId: entity.externalId },
    data: {
      kycStatus: 'approved',
      verifiedAt: new Date(),
      verifiedData: extractedData,
      isActive: true
    }
  });

  // Habilitar inicio de sesión
  await auth.enableUser(entity.externalId);

  // Otorgar acceso a servicios
  await services.grantAccess(entity.externalId, ['trading', 'withdrawal']);

  // Enviar correo de bienvenida
  await email.send({
    to: extractedData.email || entity.attributes?.email,
    subject: 'Welcome! Your account is verified',
    template: 'kyc-approved',
    data: {
      name: entity.name,
      verifiedAt: new Date().toISOString()
    }
  });

  // Registrar evento
  await audit.log({
    action: 'kyc_approved',
    entityId: entity.id,
    timestamp: new Date()
  });
}
```

### Caso de Uso 2: Monitoreo de Cumplimiento

Rastree rechazos de KYC para revisión de cumplimiento:

```javascript theme={null}
async function handleKycRejected(data) {
  const { entity, warnings, rejectionReason } = data;

  // Registrar rechazo
  await compliance.logRejection({
    entityId: entity.id,
    externalId: entity.externalId,
    reasons: warnings,
    primaryReason: rejectionReason,
    timestamp: new Date()
  });

  // Alertar al equipo de cumplimiento si hay múltiples rechazos
  const rejectCount = await db.rejections.count({
    where: { entityId: entity.id }
  });

  if (rejectCount >= 3) {
    await slack.send({
      channel: '#compliance-alerts',
      message: `🚨 Multiple KYC rejections for ${entity.name} (${entity.externalId})`,
      fields: {
        'Entity ID': entity.id,
        'Rejection Count': rejectCount,
        'Latest Reason': rejectionReason
      }
    });
  }

  // Notificar al cliente con orientación
  await email.send({
    to: entity.attributes?.email,
    subject: 'Verification unsuccessful',
    template: 'kyc-rejected',
    data: {
      name: entity.name,
      reasons: warnings,
      supportUrl: 'https://support.example.com/kyc'
    }
  });
}
```

### Caso de Uso 3: Recuperación de Verificación Abandonada

Envíe recordatorios a clientes que abandonan la verificación:

```javascript theme={null}
async function handleKycAbandoned(data) {
  const { entity, validationId, lastStep } = data;

  // Programar correo de recordatorio
  await queue.scheduleEmail({
    to: entity.attributes?.email,
    subject: 'Complete your verification',
    template: 'kyc-reminder',
    data: {
      name: entity.name,
      lastStep,
      validationUrl: `https://kyc.gu1.io/validate/${validationId}`
    },
    sendAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // Enviar en 24 horas
  });

  // Rastrear abandono
  await analytics.track({
    event: 'kyc_abandoned',
    entityId: entity.id,
    lastStep,
    timestamp: new Date()
  });
}
```

## Mejores Prácticas

<AccordionGroup>
  <Accordion title="Use entity.externalId para Búsqueda">
    El webhook incluye `entity.externalId` que es el ID que proporcionó al crear la entidad. Úselo para buscar al cliente en su base de datos.

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

  <Accordion title="Almacene IDs de Validación">
    Almacene el `validationId` de Gu1 en su base de datos. Esto le permite consultar detalles de validación más tarde si es necesario.

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

  <Accordion title="Maneje Idempotencia">
    Podría recibir el mismo webhook múltiples veces. Use el `validationId` para asegurar que procese cada evento solo una vez.

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

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

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

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

  <Accordion title="Devuelva 200 Rápidamente">
    Siempre devuelva un código de estado 200 lo más rápido posible para confirmar recepción. Procese el webhook asincrónicamente si es necesario.

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

      // Procesar asincrónicamente
      processWebhook(req.body).catch(console.error);
    });
    ```
  </Accordion>

  <Accordion title="Verifique Firmas">
    Siempre verifique el header `X-Webhook-Signature` para asegurar que el webhook sea auténtico. Vea la [guía de seguridad](/es/webhooks/security) para detalles.

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

  <Accordion title="Maneje Errores con Gracia">
    Si el procesamiento falla, registre el error pero aún devuelva 200 para evitar reintentos. Almacene webhooks fallidos para revisión manual.

    ```javascript theme={null}
    try {
      await processWebhook(payload);
    } catch (error) {
      await db.saveFailedWebhook({
        payload,
        error: error.message,
        receivedAt: new Date()
      });

      // Aún así devolver 200
      res.status(200).json({ success: false });
    }
    ```
  </Accordion>
</AccordionGroup>

## Solución de Problemas

<AccordionGroup>
  <Accordion title="No Recibo Webhooks">
    **Verificar estos elementos:**

    * La URL del webhook es públicamente accesible vía HTTPS
    * El webhook está configurado y **habilitado** en el dashboard
    * Suscrito a tipos de eventos KYC correctos
    * El endpoint devuelve código de estado 200 dentro de 30 segundos
    * Verificar logs del servidor para solicitudes entrantes
  </Accordion>

  <Accordion title="Falta extractedData">
    `extractedData` y `verifiedFields` solo se incluyen en:

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

    No están presentes en otros tipos de eventos como `validation_created` o `validation_in_progress`.
  </Accordion>

  <Accordion title="Falla la Verificación de Firma">
    **Causas comunes:**

    * Usar secreto incorrecto (verificar dashboard para secreto actual)
    * Verificar firma en JSON analizado en lugar de cuerpo raw
    * Re-verificar desde el JSON del **monitor de webhooks** (pretty-print / `JSON.stringify` ≠ bytes firmados)
    * Middleware que muta el body antes de verificar (algunos payloads fallan, otros pasan)
    * Secreto no guardado correctamente después de creación del webhook
    * Problemas de codificación (asegurar UTF-8)

    Vea [Seguridad de webhooks](/es/webhooks/security) — en especial *Cuerpo sin procesar vs JSON analizado*, *Historial en el dashboard* y *Fallos intermitentes de firma*.
  </Accordion>

  <Accordion title="Recibo Webhooks Duplicados">
    Este es un comportamiento normal. Los webhooks pueden enviarse múltiples veces debido a problemas de red, tiempos de espera o reintentos.

    **Siempre implemente idempotencia** usando el `validationId` del webhook y tipo de `event`.
  </Accordion>
</AccordionGroup>

## Próximos Pasos

<CardGroup cols={2}>
  <Card title="Eventos de Entidades" icon="user" href="/es/webhooks/events/entity-events">
    Maneje eventos del ciclo de vida de entidades
  </Card>

  <Card title="Eventos de Reglas" icon="gavel" href="/es/webhooks/events/rule-events">
    Procese activaciones de reglas de cumplimiento
  </Card>

  <Card title="Seguridad de Webhooks" icon="shield" href="/es/webhooks/security">
    Asegure sus endpoints de webhook
  </Card>

  <Card title="Configuración" icon="gear" href="/es/webhooks/configuration">
    Configure ajustes de webhook
  </Card>
</CardGroup>
