XIP

Documentos institucionales  /  Evidencia de Registros de KYC

Evidencia

Evidencia de Registros de KYC

Demostración estructurada de los registros de verificación de identidad que mantiene XIP, con datos enmascarados, y de la arquitectura de custodia de los documentos de identificación.

Documento EVD-01
Versión 1.0
Vigente desde 29/07/2026
Última revisión 29/07/2026
Clasificación Público

Esta es una traducción de cortesía. La versión en portugués es el texto auténtico y prevalece en caso de divergencia.

Aviso esencial sobre los datos de este documento

Todos los registros exhibidos aquí son sintéticos: fueron construidos para este documento y no corresponden a personas reales ni a operaciones reales. Reproducen fielmente la estructura, los tipos, los estados y las relaciones de la base de producción, pero ningún dato de usuario real —aun enmascarado— se publica en esta página.

La evidencia equivalente extraída del entorno de producción, con datos reales enmascarados, se proporciona a socios, auditores y autoridades mediante acuerdo de confidencialidad (NDA). Ver la sección final de este documento.

Objetivo y método

Este documento evidencia, de forma verificable, cómo XIP registra y conserva el resultado de la verificación de identidad de sus usuarios y dónde residen los documentos de identificación.

Responde a cuatro preguntas que suelen formularse en la diligencia de socios y de autoridades:

  1. ¿Qué información de KYC guarda efectivamente XIP, y en qué estructura?
  2. ¿Dónde están las imágenes de documento y la imagen facial, si no están en XIP?
  3. ¿Es cierto que un usuario no verificado no consigue transaccionar? ¿Cómo se comprueba?
  4. ¿Cómo puede un tercero reproducir esta verificación de forma independiente?

El método adoptado es la presentación del esquema real de las tablas, de consultas reproducibles y de resultados de esas consultas sobre un conjunto sintético que cubre todos los estados posibles del registro. Las consultas son las mismas aplicables a la base de producción.

Arquitectura de custodia

La distinción que sigue es la clave para leer todas las evidencias de este documento.

Elemento Dónde reside Quién lo custodia
Imagen del documento de identificación (frente y reverso, o CNH, la licencia de conducir brasileña) Fuera de XIP Prestador contratado — institución autorizada
Imagen facial (selfie / prueba de vida) Fuera de XIP Prestador contratado
Tipo de documento presentado Fuera de XIP Prestador contratado
Número de teléfono Fuera de XIP Prestador contratado
Nombre completo y CPF (el número de registro fiscal de personas físicas de Brasil) Base de datos de XIP — tabla receivers XIP y prestador contratado
Resultado de la verificación, motivo del rechazo y fecha del análisis Base de datos de XIP — tabla receivers XIP
Identificador del registro en el prestador Base de datos de XIP — tabla receivers XIP
Respuesta bruta del prestador (pista de auditoría) Base de datos de XIP — columna raw_payload XIP

Es decir: XIP guarda el registro y el resultado de la verificación; el prestador contratado guarda los archivos que la fundamentaron. La correlación entre ambos lados se hace mediante el identificador del registro, lo que permite auditoría de extremo a extremo sin replicación de documentos.

Evidencia A — Esquema de la tabla de registros

Estructura real de la tabla receivers, que almacena el registro de verificación de cada usuario. Salida del comando de descripción de tabla de PostgreSQL:

xip=> \d receivers

                                       Table "public.receivers"
        Column         |            Type             | Nullable |              Default
-----------------------+-----------------------------+----------+-------------------------------
 id                    | bigint                      | not null | generated always as identity
 user_id               | bigint                      | not null |
 provider              | character varying(32)       | not null |
 gateway_receiver_id   | character varying(64)       | not null |
 document              | character varying(32)       | not null |
 name                  | character varying(255)      | not null |
 wallet_address        | character varying(64)       | not null |
 status                | character varying(16)       | not null |
 kyc_status            | character varying(16)       | not null |
 kyc_rejection_reason  | text                        |          |
 kyc_reviewed_at       | timestamp without time zone |          |
 raw_payload           | jsonb                       |          |
 created_at            | timestamp without time zone |          |
 updated_at            | timestamp without time zone |          |
 deleted_at            | timestamp without time zone |          |

Indexes:
    "receivers_pkey" PRIMARY KEY, btree (id)
    "receivers_user_id_provider_unique" UNIQUE CONSTRAINT, btree (user_id, provider)
    "receivers_provider_gateway_receiver_id_unique" UNIQUE CONSTRAINT, btree (provider, gateway_receiver_id)
    "receivers_user_id_index" btree (user_id)
    "receivers_provider_index" btree (provider)
    "receivers_status_index" btree (status)
    "receivers_kyc_status_index" btree (kyc_status)

Foreign-key constraints:
    "receivers_user_id_foreign" FOREIGN KEY (user_id) REFERENCES users(id)

Lo que comprueba el esquema

Observación Consecuencia de control
No existe ninguna columna binaria — ningún bytea, blob, ningún campo de ruta de archivo ni URL de imagen Es estructuralmente imposible que imágenes de documento o faciales estén almacenadas en esta tabla. No hay dónde guardarlas.
Restricción de unicidad en (user_id, provider) Un único registro de verificación por usuario y por prestador. La duplicidad la rechaza la base de datos, no una convención de código.
Restricción de unicidad en (provider, gateway_receiver_id) El identificador externo es unívoco: dos usuarios no pueden apuntar al mismo registro en el prestador.
Clave ajena user_id → users(id), no nula Todo registro de verificación está obligatoriamente vinculado a una cuenta. No existe registro huérfano ni anónimo.
Columnas kyc_status y status indexadas y no nulas El estado está siempre definido y es consultable con eficiencia — condición para la verificación de habilitación ejecutada en cada operación.
Columna kyc_reviewed_at Registra el momento de la decisión, lo que permite comprobar la tempestividad del análisis.
Columna raw_payload en jsonb Preserva la respuesta íntegra del prestador, lo que permite una conciliación independiente de lo que XIP interpretó.
Columna deleted_at (borrado lógico) Los registros no son borrados físicamente por la aplicación, lo que preserva la pista para los plazos legales de conservación.

Evidencia B — Reglas de enmascaramiento aplicadas

Las consultas de evidencia aplican las reglas siguientes. Son deterministas y no reversibles: a partir del valor enmascarado no es posible reconstituir el original.

Campo Regla Ejemplo de salida
CPF (document) Preserva los 3 primeros dígitos; enmascara los 8 restantes, incluidos los dígitos verificadores 123.***.***-**
Nombre (name) Preserva el nombre de pila; reduce cada apellido a la inicial seguida de asteriscos MARIANA A*** S****
Dirección de billetera Preserva los 4 primeros y los 4 últimos caracteres 7Xk2…f9Qa
Identificador en el prestador Preserva el prefijo del tipo y los 4 últimos caracteres rcv_…a3f9
Nombre del prestador (provider) Sustituido por una etiqueta neutra en esta publicación psp-01
Correo electrónico Preserva la primera letra de la parte local y la extensión del dominio m****@*****.com

Evidencia C — Registros de verificación

Consulta al conjunto de demostración, con enmascaramiento aplicado. El conjunto fue construido para cubrir todos los estados posibles de verificación y de habilitación.

Bloque de identificación

xip=> SELECT id,
xip->        user_id,
xip->        provider,
xip->        mask_ext_id(gateway_receiver_id) AS gateway_receiver_id,
xip->        mask_cpf(document)               AS document,
xip->        mask_name(name)                  AS name,
xip->        mask_wallet(wallet_address)      AS wallet_address
xip->   FROM receivers
xip->  ORDER BY id;

 id | user_id | provider | gateway_receiver_id |    document    |         name         | wallet_address
----+---------+----------+---------------------+----------------+----------------------+----------------
  1 |    1041 | psp-01   | rcv_…a3f9           | 123.***.***-** | MARIANA A*** S****   | 7Xk2…f9Qa
  2 |    1042 | psp-01   | rcv_…b7c1           | 087.***.***-** | CARLOS E***** L****  | 9Fm4…2Rte
  3 |    1043 | psp-01   | rcv_…c2d8           | 341.***.***-** | JULIANA P***** M**** | 4Bqz…8Lkw
  4 |    1044 | psp-01   | rcv_…d9e3           | 512.***.***-** | RAFAEL T****        | Hn6v…3Yxs
  5 |    1045 | psp-01   | rcv_…e4f7           | 209.***.***-** | BEATRIZ O***** C***  | 2Kdp…7Mnb
  6 |    1046 | psp-01   | rcv_…f1a5           | 763.***.***-** | ANDRE L**** F*****   | Qw8r…5Zjt
  7 |    1047 | psp-01   | rcv_…a8b2           | 145.***.***-** | PATRICIA G***** N*** | Vc3y…9Hdl
  8 |    1048 | psp-01   | rcv_…b5c9           | 690.***.***-** | THIAGO R***** D***   | Ls7f…4Pqw
(8 rows)

Bloque de estado y decisión

xip=> SELECT id,
xip->        kyc_status,
xip->        status,
xip->        kyc_rejection_reason,
xip->        kyc_reviewed_at,
xip->        created_at
xip->   FROM receivers
xip->  ORDER BY id;

 id | kyc_status   | status    | kyc_rejection_reason                    | kyc_reviewed_at     | created_at
----+--------------+-----------+-----------------------------------------+---------------------+---------------------
  1 | APPROVED     | ACTIVE    |                                         | 2026-06-14 11:23:07 | 2026-06-14 10:58:41
  2 | APPROVED     | ACTIVE    |                                         | 2026-06-21 09:15:52 | 2026-06-21 08:47:19
  3 | UNDER_REVIEW | PENDING   |                                         |                     | 2026-07-27 16:02:33
  4 | SUBMITTED    | PENDING   |                                         |                     | 2026-07-28 19:44:10
  5 | REJECTED     | PENDING   | Documento ilegível: verso do RG fora    | 2026-07-22 14:31:26 | 2026-07-22 13:55:08
    |              |           | de foco. Reenviar com melhor iluminação |                     |
  6 | APPROVED     | SUSPENDED |                                         | 2026-05-30 10:07:44 | 2026-05-30 09:39:15
  7 | APPROVED     | BLOCKED   |                                         | 2026-04-18 15:52:31 | 2026-04-18 15:20:02
  8 | PENDING      | PENDING   |                                         |                     | 2026-07-29 08:11:57
(8 rows)

Lectura de las filas relevantes:

  • Filas 1 y 2 — los únicos registros habilitados para transaccionar: verificación aprobada y registro activo.
  • Filas 3 y 4 — en curso; la fecha del análisis está vacía porque no hubo decisión.
  • Fila 5 — rechazado con motivo registrado y legible, mostrado al usuario para corrección y nueva presentación.
  • Fila 6 — verificación aprobada, pero registro suspendido: no transacciona. Demuestra que la aprobación documental no es una autorización permanente.
  • Fila 7 — verificación aprobada, registro bloqueado: no transacciona.
  • Fila 8 — registro creado, documentos aún no enviados.

Evidencia D — Distribución de estados

Consulta agregada que permite comprobar, en cualquier momento, la composición de la base por estado — indicador de seguimiento previsto en la Política de KYC y EDD:

xip=> SELECT kyc_status,
xip->        status,
xip->        count(*) AS cadastros,
xip->        bool_and(kyc_status = 'APPROVED' AND status = 'ACTIVE') AS habilitado
xip->   FROM receivers
xip->  GROUP BY kyc_status, status
xip->  ORDER BY kyc_status, status;

 kyc_status   | status    | cadastros | habilitado
--------------+-----------+-----------+------------
 APPROVED     | ACTIVE    |         2 | t
 APPROVED     | BLOCKED   |         1 | f
 APPROVED     | SUSPENDED |         1 | f
 PENDING      | PENDING   |         1 | f
 REJECTED     | PENDING   |         1 | f
 SUBMITTED    | PENDING   |         1 | f
 UNDER_REVIEW | PENDING   |         1 | f
(7 rows)

De los 8 registros, solo 2 satisfacen la condición de habilitación. Los 6 restantes están impedidos de transaccionar, cada uno por un motivo distinto y registrado.

Evidencia E — Pista de auditoría del prestador

La columna raw_payload conserva la respuesta íntegra recibida del prestador contratado en cada evento del ciclo de vida del registro — creación, envío de documentos, actualización y consulta. Esto permite conciliar de forma independiente lo que el prestador afirmó y lo que XIP registró.

Ejemplo del contenido conservado para el registro rechazado (fila 5), con enmascaramiento aplicado:

xip=> SELECT jsonb_pretty(mask_payload(raw_payload)) FROM receivers WHERE id = 5;

{
    "id": "rcv_…e4f7",
    "status": "PENDING",
    "kycStatus": "REJECTED",
    "kycRejectionReason": "Documento ilegível: verso do RG fora de foco. Reenviar com melhor iluminação",
    "name": "BEATRIZ O***** C***",
    "document": "209.***.***-**",
    "email": "b****@*****.com",
    "walletAddress": "2Kdp…7Mnb"
}
Obsérvese lo que no está presente

La respuesta del prestador contratado no devuelve, y por lo tanto XIP no conserva, ninguna representación de las imágenes enviadas — ni contenido, ni dirección, ni identificador de archivo. Devuelve únicamente el resultado del análisis y los datos de identificación ya conocidos.

Evidencia F — Registro de eventos recibidos

Los eventos asíncronos comunicados por el prestador contratado se registran en una tabla propia, con control de unicidad y registro de error de procesamiento. Estructura:

xip=> \d webhook_events

                                    Table "public.webhook_events"
      Column       |            Type             | Nullable |              Default
-------------------+-----------------------------+----------+-------------------------------
 id                | bigint                      | not null | generated always as identity
 provider          | character varying(32)       | not null |
 event_id          | character varying(128)      | not null |
 event_type        | character varying(64)       | not null |
 external_ref      | character varying(64)       |          |
 transaction_id    | bigint                      |          |
 received_at       | timestamp without time zone | not null |
 processed_at      | timestamp without time zone |          |
 processing_error  | text                        |          |
 raw_payload       | jsonb                       |          |
 created_at        | timestamp without time zone |          |
 updated_at        | timestamp without time zone |          |

Indexes:
    "webhook_events_pkey" PRIMARY KEY, btree (id)
    "webhook_events_provider_event_id_unique" UNIQUE CONSTRAINT, btree (provider, event_id)
    "webhook_events_provider_external_ref_index" btree (provider, external_ref)
    "webhook_events_transaction_id_index" btree (transaction_id)

Controles evidenciados por esta estructura:

  • la restricción de unicidad en (provider, event_id) hace que el procesamiento sea idempotente: el mismo evento no puede aplicarse dos veces, aunque sea retransmitido;
  • received_at es obligatorio y processed_at es opcional, lo que preserva el registro de eventos recibidos y no procesados — un fallo no borra la evidencia de que el evento llegó;
  • processing_error conserva la causa del fallo, lo que permite su averiguación posterior;
  • raw_payload conserva el mensaje original recibido.

Adicionalmente, y antes de cualquier registro, toda notificación recibida se somete a verificación de firma HMAC-SHA256 sobre el cuerpo bruto del mensaje. Las notificaciones sin firma válida son rechazadas, y no existe configuración que permita aceptar un mensaje no firmado.

Evidencia G — Bloqueo de operación sin habilitación

Esta es la evidencia más consecuente del documento: la demostración de que el impedimento de transaccionar es estructural, y no una comprobación manual sujeta a fallo.

G.1 — Condición en el código

La habilitación se define en el modelo de datos, en cuatro métodos, y es la misma para entrada y para salida:

// app/models/receiver.go

// IsApproved reports whether the gateway approved the user's KYC.
func (r *Receiver) IsApproved() bool { return r.KycStatus == KycStatusApproved }

// IsActive reports whether the receiver is enabled to transact.
func (r *Receiver) IsActive() bool { return r.Status == ReceiverStatusActive }

// CanPayin reports whether the receiver may originate a payin (approved + active).
func (r *Receiver) CanPayin() bool { return r.IsApproved() && r.IsActive() }

// CanPayout reports whether the receiver may originate a payout. The gateway
// applies the same eligibility as payin: KYC approved and receiver active.
func (r *Receiver) CanPayout() bool { return r.IsApproved() && r.IsActive() }

G.2 — Aplicación de la condición en cada operación

Los servicios de entrada y de salida resuelven el registro del usuario y rechazan la operación antes de cualquier efecto externo — sin generar cotización y sin llamar al prestador contratado:

// app/services/client/payin/payin_service.go
if rec == nil || !rec.CanPayin() {
    return nil, ErrKycRequired
}

// app/services/client/payout/payout_service.go
if rec == nil || !rec.CanPayout() {
    return nil, ErrKycRequired
}

Observaciones relevantes:

  • la ausencia de registro (rec == nil) se trata como fallo de la condición, del mismo modo que un registro rechazado o suspendido — no hay camino implícito de permiso;
  • la verificación está en la capa de servicio del servidor, no en la aplicación: modificar el cliente o construir solicitudes manualmente no la elude;
  • el criterio es literalmente el mismo para entrada y salida, sin diferenciación por importe ni por antigüedad de la cuenta.

G.3 — Comprobación sobre los datos

Consulta que confronta el estado de cada registro con el volumen de operaciones efectivamente registradas para el usuario:

xip=> SELECT r.id,
xip->        r.kyc_status,
xip->        r.status,
xip->        (r.kyc_status = 'APPROVED' AND r.status = 'ACTIVE') AS habilitado,
xip->        count(t.id) AS operacoes
xip->   FROM receivers r
xip->   LEFT JOIN transactions t ON t.user_id = r.user_id
xip->  GROUP BY r.id, r.kyc_status, r.status
xip->  ORDER BY r.id;

 id | kyc_status   | status    | habilitado | operacoes
----+--------------+-----------+------------+-----------
  1 | APPROVED     | ACTIVE    | t          |        14
  2 | APPROVED     | ACTIVE    | t          |         3
  3 | UNDER_REVIEW | PENDING   | f          |         0
  4 | SUBMITTED    | PENDING   | f          |         0
  5 | REJECTED     | PENDING   | f          |         0
  6 | APPROVED     | SUSPENDED | f          |         0
  7 | APPROVED     | BLOCKED   | f          |         0
  8 | PENDING      | PENDING   | f          |         0
(8 rows)

El resultado es categórico: toda fila con habilitado = f presenta cero operaciones. Ninguna excepción. La consulta siguiente formaliza la aserción y debe devolver siempre vacío:

xip=> -- Deve retornar 0 linhas. Qualquer linha aqui é uma violação de controle.
xip=> SELECT t.id, t.user_id, t.type, t.status, r.kyc_status, r.status
xip->   FROM transactions t
xip->   JOIN receivers r ON r.user_id = t.user_id
xip->  WHERE NOT (r.kyc_status = 'APPROVED' AND r.status = 'ACTIVE');

 id | user_id | type | status | kyc_status | status
----+---------+------+--------+------------+--------
(0 rows)

Nótese además que la fila 6 — verificación aprobada, registro suspendido — registra cero operaciones, lo que evidencia que la suspensión tiene efecto inmediato y que la aprobación documental anterior no confiere autorización remanente.

Evidencia H — Ausencia de persistencia de imágenes

La afirmación de que XIP no almacena imágenes de documento ni imágenes faciales es verificable por tres medios independientes, todos reproducibles.

H.1 — Ninguna columna capaz de almacenar una imagen

Consulta al catálogo de la base de datos, que enumera toda columna de tipo binario o con nombre sugestivo de archivo en todo el esquema:

xip=> SELECT table_name, column_name, data_type
xip->   FROM information_schema.columns
xip->  WHERE table_schema = 'public'
xip->    AND (data_type IN ('bytea', 'blob')
xip->         OR column_name ~* '(selfie|document_front|document_back|document_photo|image|photo|file|attachment|upload)')
xip->  ORDER BY table_name, column_name;

 table_name | column_name | data_type
------------+-------------+-----------
(0 rows)

Ninguna columna en todo el esquema es capaz de almacenar una imagen, y ninguna referencia un archivo de documento.

H.2 — Ninguna escritura en almacenamiento de archivos

Búsqueda en el código fuente de cualquier uso de la capa de almacenamiento de archivos de la aplicación:

$ grep -rn "facades.Storage()" app/ --include="*.go"
$ echo "resultado: nenhuma ocorrência"
resultado: nenhuma ocorrência

La aplicación no utiliza, en ningún punto, la facilidad de almacenamiento de archivos del framework — no hay escritura en disco local, en volumen ni en servicio de almacenamiento de objetos.

H.3 — Encaminamiento directo, en memoria

El fragmento responsable del envío de los documentos monta la solicitud en memoria a partir de los archivos recibidos y la transmite inmediatamente al prestador contratado. No hay etapa intermedia de escritura:

// SubmitKYCRequest carries the multipart KYC documents. Files are *multipart
// .FileHeader values streamed straight from the inbound HTTP request — nothing
// is buffered to disk by us. For documentType "rg": Selfie, DocumentFront,
// DocumentBack. For "cnh": Selfie, DocumentPhoto.
type SubmitKYCRequest struct {
    DocumentType  string
    Selfie        *multipart.FileHeader
    DocumentFront *multipart.FileHeader
    DocumentBack  *multipart.FileHeader
    DocumentPhoto *multipart.FileHeader
}

Terminada la solicitud HTTP, las estructuras en memoria son liberadas por el recolector de basura del proceso. Ningún vestigio persiste.

Por qué importa esta evidencia

Las tres verificaciones son de naturalezas distintas — catálogo de la base de datos, búsqueda en el código y lectura del flujo — y convergen. Un compromiso de la base de datos de XIP no expondría imágenes de documento ni imágenes faciales, porque no están allí en ninguna forma.

Cómo reproducir esta verificación

Un socio, auditor o autoridad puede reproducir íntegramente las evidencias de este documento. Las consultas presentadas son las mismas aplicables a la base de producción; solo las funciones de enmascaramiento necesitan estar disponibles en la sesión.

Evidencia Cómo reproducir
Esquema de las tablas (A, F) \d receivers y \d webhook_events en sesión de lectura.
Registros y estados (C, D) Ejecutar las consultas presentadas con las funciones de enmascaramiento aplicadas.
Pista del prestador (E) Consultar raw_payload del registro bajo examen.
Bloqueo de operación (G) Ejecutar la consulta de aserción de G.3 y confirmar el retorno vacío; inspeccionar los fragmentos de código indicados en G.1 y G.2.
Ausencia de imágenes (H) Ejecutar la consulta al catálogo de H.1 y la búsqueda en el código de H.2.

Complementariamente, el comportamiento del bloqueo está cubierto por pruebas automatizadas ejecutadas en cada modificación del código, que ejercitan el flujo de verificación y confirman el rechazo de operaciones para registros no habilitados. Los informes de ejecución se ponen a disposición a solicitud.

Evidencia con datos de producción

Esta página es pública y, por esa razón, exhibe exclusivamente datos sintéticos. La publicación de registros reales, aun enmascarados, ampliaría sin necesidad la exposición de datos personales de usuarios — lo que sería incompatible con el principio de minimización adoptado en la Política de Protección de Datos y Privacidad.

Para socios en proceso de diligencia, auditores independientes y autoridades competentes, ponemos a disposición, mediante acuerdo de confidencialidad:

  • las mismas consultas ejecutadas sobre la base de producción, con enmascaramiento aplicado, en un informe fechado y firmado;
  • demostración asistida en entorno controlado, con seguimiento en tiempo real de la ejecución de las consultas;
  • informes de ejecución de las pruebas automatizadas que cubren el flujo de verificación y el bloqueo transaccional;
  • evidencias relativas a los archivos de identificación bajo custodia del prestador contratado, obtenidas de él y por él atestiguadas, en cuanto a existencia, integridad, plazo de guarda y controles de acceso;
  • documentación contractual que establece las obligaciones de cumplimiento del prestador contratado.

Las solicitudes deben dirigirse a compliance@xip.cash.

Historial de versiones

Versión Fecha Cambios
1.0 29/07/2026 Publicación inicial.