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.
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:
- ¿Qué información de KYC guarda efectivamente XIP, y en qué estructura?
- ¿Dónde están las imágenes de documento y la imagen facial, si no están en XIP?
- ¿Es cierto que un usuario no verificado no consigue transaccionar? ¿Cómo se comprueba?
- ¿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"
}
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_ates obligatorio yprocessed_ates opcional, lo que preserva el registro de eventos recibidos y no procesados — un fallo no borra la evidencia de que el evento llegó;processing_errorconserva la causa del fallo, lo que permite su averiguación posterior;raw_payloadconserva 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.
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. |