Legal y KYCVerificación de identidad (KYC) con Didit
Legal y KYC

Verificación de identidad (KYC) con Didit

1. Qué es KYC en Structura KYC (Know Your Customer) es la verificación de identidad del Owner del Tenant. Confirma que la persona detrás de un comercio es quien dice ser, usando…

Guía de Structura MarketAbrir pantalla

¿Querés hacerlo ahora?

Abrí la pantalla correspondiente y seguí esta guía mientras configurás.

Abrir pantalla

1. Qué es KYC en Structura

KYC (*Know Your Customer*) es la verificación de identidad del Owner del Tenant. Confirma

que la persona detrás de un comercio es quien dice ser, usando el proveedor externo Didit

(Hosted Session).

2. Por qué es opcional en esta versión

El MVP actual nunca bloquea ventas, pedidos, storefront ni ninguna operación del Tenant por

no tener KYC aprobado. Es un paso opcional del checklist "Configurá tu tienda" y un banner

informativo en el Home — nada más. Una versión futura podría atarlo a habilitar funcionalidades

puntuales de cobros, pero eso no está implementado todavía.

3. Quién puede iniciarlo

Solo el Owner del Tenant (o super_admin de plataforma). La autorización se valida

server-side en cada request — nunca confía en lo que declare el frontend.

4. Configurar las credenciales de Didit

Variables de entorno (nunca en el código, nunca en el frontend):

  • DIDIT_API_KEY — API key de Didit (header x-api-key en cada request al backend de Didit).
  • DIDIT_WORKFLOW_ID — UUID del workflow de verificación elegido en el panel de Didit

(Workflows → copiar ID completo, no el truncado que se ve en la tabla).

  • KYC_WEBHOOK_SECRET — el "Webhook Secret Key" que Didit muestra una sola vez al crear

el destino del webhook en *Business Console → API & Webhooks*. Si se pierde, hay que borrar

el destino y crearlo de nuevo — Didit no lo vuelve a mostrar.

En local van en .env (nunca se commitea). En producción, como variables de entorno del sitio

en Netlify.

5. Configurar el workflow

En el panel de Didit (*Workflows*), cada workflow define qué se verifica (documento, biometría,

AML, etc.) y tiene su propio costo por verificación. El workflow_id se copia desde ahí y se

carga en DIDIT_WORKFLOW_ID — nunca se hardcodea en el código.

6. Cómo se crea una sesión

El Owner aprieta "Verificar identidad" en Mi Cuenta. El frontend llama a

POST /api/kyc/session (requiere ser Owner y que el workspace tenga habilitado el switch de

Legal+KYC). El backend llama a Didit (POST /v3/session/) pasando workflow_id,

vendor_data (el tenantId, para poder resolver el tenant en el webhook) y una URL de

callback para volver a Mi Cuenta al terminar.

7. Cómo se abre la experiencia de verificación

El backend devuelve una url de Didit. El frontend redirige la página completa a esa URL

(no un popup) — los popups suelen bloquear el acceso a la cámara en navegadores móviles, y la

mayoría de los workflows piden foto de documento y/o selfie.

8. Cómo funciona el webhook

Didit notifica cambios de estado a POST /api/kyc/webhook. Cada request trae:

  • X-Signature — firma HMAC-SHA256 sobre el body crudo.
  • X-Timestamp — epoch en segundos del envío.
  • Body JSON con event_id, session_id, status, vendor_data (el tenantId) y,

cuando corresponde, decision.

9. Cómo se valida la firma

Se recalcula HMAC-SHA256 del body crudo (antes de parsear JSON) con KYC_WEBHOOK_SECRET

y se compara contra X-Signature con timingSafeEqual (comparación a tiempo constante, evita

timing attacks). Si no hay secreto configurado, el webhook rechaza todo — nunca procesa sin

poder verificar la firma. También se rechaza si X-Timestamp está a más de 300 segundos del

reloj del servidor (anti-replay).

10. Cómo se persiste el resultado

En sm_kyc_records (una fila por tenant/provider): status, provider_session_id,

rejection_code, approved_at. La idempotencia por event_id vive en

sm_kyc_webhook_events — un event_id repetido (reintento de Didit) no reprocesa nada.

11. Qué datos se guardan y cuáles nunca

Se guarda: tenant, provider, session id, status, código de rechazo, timestamps.

Nunca se guarda: DNI/pasaporte, imágenes del documento, selfie, biometría, ni el payload

completo que manda Didit — solo se extraen los campos de status.

12. Qué pasa ante cada resultado

  • Aprobado → approved, se guarda approved_at. El banner del Home desaparece.
  • Rechazado → rejected + rejection_code si Didit lo informó. El Owner puede reintentar.
  • Expirado / abandonado → expired. Se puede iniciar una verificación nueva.
  • Error de configuración (falta DIDIT_API_KEY/DIDIT_WORKFLOW_ID) → 503, sin crear

sesión.

13. Cómo probar el flujo en sandbox

Didit permite simular resultados deterministas con sandbox_scenario en modo sandbox. Para el

flujo real end-to-end (Owner → Mi Cuenta → Didit → webhook → estado actualizado) conviene

probarlo desde un celular real o el simulador iOS/Android, porque la mayoría de los workflows

piden cámara.

14. Cómo diagnosticar una sesión que quedó pendiente

  1. Revisar Admin → Legal → KYC (/admin/ops/legal/kyc): filtrar por tenant o status.
  2. Si Didit no mandó webhook, se puede consultar el fallback

GET /v3/session/{session_id}/decision/ (implementado en getDiditSessionDecision).

  1. Si sigue sin resolverse, un super_admin puede hacer un override manual (aprobar/rechazar)

desde esa misma pantalla — queda auditado en sm_audit_logs como KYC_ADMIN_OVERRIDE.

15. Cómo rotar/revocar la API key

Se regenera desde el panel de Didit y se actualiza DIDIT_API_KEY en las variables de entorno

(local y Netlify). No requiere cambios de código. El KYC_WEBHOOK_SECRET es independiente —

rotarlo implica recrear el destino del webhook en Didit.

16. Variables de entorno requeridas por ambiente

VariableLocal (.env)Producción (Netlify)
DIDIT_API_KEYsísí
DIDIT_WORKFLOW_IDsísí
KYC_WEBHOOK_SECRETsísí

17. Evidencia de pruebas

Cobertura unitaria en tests/unit/kyc-didit-system.test.ts: mapeo de estados, verificación de

firma, ventana anti-replay, idempotencia, minimización de datos y el switch de plataforma

legal_kyc_gate. El E2E manual (Owner → Didit → webhook → estado) se corre a mano por ahora

— no hay automation de cámara/documento en CI.