Skip to content

11. Compliance & Consent

Consent, data-subject rights (export, deletion, withdrawal), audit logging, and retention.

When enabled: consent_required: true

The platform provides consent recording, status tracking, and enforcement. When enabled, the platform blocks palm enrollment and device-initiated transactions (/v1/device/enroll, /v1/device/transactions, plus the /v1/enroll challenge) if the user has not granted consent — returning HTTP 403 with error code consent_required. (Under the small model (§8.13) the platform is off the enroll path, so the device enforces this gate before the SDK enrolls; the platform then records the result.) API responses also include consent_status (e.g., "none" or "granted") so callers can show appropriate UI before attempting these operations.

Consent Capture Points: - Product app / dashboard: User grants consent during onboarding via POST /v1/consent (user-JWT). When they later visit a device, consent is already on record. - At the device (pos/kiosk): The device shows a consent screen and records consent via POST /v1/device/consent (mTLS) before enrollment — no vertical backend in the path (§13). - Integrator-managed: External integrators capture consent in their own systems and record it via POST /v1/consent before calling enrollment/transaction endpoints.

API Endpoints: See Section 14.1 for consent endpoints (record, check, withdraw). The consent_records schema is in §5.5.

11.2 Data Subject Rights

When enabled: data_subject_rights_enabled: true

  • GET /v1/users/{id}/data — Export all user data (profile, identities, KYC, consent records, palm metadata). Palm templates are NOT exported.
  • DELETE /v1/users/{id} — Delete user. Anonymizes PII, removes palm template from vendor, revokes tokens.

Diagram 7.1 — User Data Export (Right to Access)

sequenceDiagram
    autonumber
    participant User
    participant App as Mobile App
    participant Vertical as Vertical Backend
    participant Identity as Identity Platform
    participant Palm as X-Telcom BioWave Pass
    participant DB as PostgreSQL
    participant Audit as Audit Log

    Note over Identity, Audit: All audit logging is conditional<br/>on tenant audit_enabled setting

    User->>App: Request my data
    App->>Vertical: POST /user/data-export
    Vertical->>Identity: GET /v1/users/{user_id}/data<br/>Authorization: Bearer <token>

    Identity->>DB: Get tenant config
    DB-->>Identity: {data_subject_rights_enabled: true}

    Identity->>DB: Fetch all user data
    DB-->>Identity: User profile, identities,<br/>KYC data, consent records

    rect rgb(255, 245, 230)
        Note over Identity, Palm: Active palm metadata
        Identity->>Palm: POST /KZ/query_palm_id<br/>Headers: request_id: <uuid><br/>Content-Type: application/json<br/>Body: {user_id}
        Palm-->>Identity: {code: 0,<br/>data: {user_id, query_type,<br/>id_data_list: [{id, type}, ...]}}
        Note over Identity: Template data NOT exported<br/>(only metadata: palm_id + type)
    end

    opt Include historical (deleted) palm metadata
        Note over Identity, Palm: Forensic export — requires<br/>a current palm scan (only<br/>available if user can re-scan)
        Identity->>Palm: POST /KZ/deleted_records<br/>Headers: request_id: <uuid><br/>Content-Type: multipart/form-data<br/>Body:<br/>- features_rgb.bin (optional)<br/>- features_ir.bin (optional)<br/>- image_rgb.png (required)<br/>- image_ir.png (required)<br/>- metadata: {type: "left",<br/>is_encrypted: false}
        Palm-->>Identity: {code: 0,<br/>data: {deleted_records: [{user_id, id,<br/>type, scores: [4 floats],<br/>add_date_time, delete_date_time}, ...]}}
    end

    Identity->>Audit: Log: data_export<br/>(user_id, requester: user)

    Identity-->>Vertical: {<br/>  user: {id, created_at, status},<br/>  identities: [{type, value, verified}],<br/>  profile: {name, dob, ...},<br/>  kyc: {status, provider, verified_at},<br/>  consent: [{type, granted_at, purposes}],<br/>  palm: {enrolled: true, type: "left",<br/>         enrolled_at,<br/>         deleted_history?: [...]}<br/>}

    Vertical-->>App: Formatted data export
    App-->>User: Download / View data

Diagram 7.2 — User Deletion (Right to Erasure)

sequenceDiagram
    autonumber
    participant User
    participant App as Mobile App
    participant Vertical as Vertical Backend
    participant Identity as Identity Platform
    participant Palm as X-Telcom BioWave Pass
    participant DB as PostgreSQL
    participant Audit as Audit Log

    Note over Identity, Audit: All audit logging is conditional<br/>on tenant audit_enabled setting

    User->>App: Delete my account
    App->>Vertical: DELETE /user/account
    Vertical->>Identity: DELETE /v1/users/{user_id}<br/>Authorization: Bearer <token>

    Identity->>DB: Get tenant config
    DB-->>Identity: {data_subject_rights_enabled: true}

    Identity->>DB: Get user
    DB-->>Identity: User found

    rect rgb(255, 230, 230)
        Note over Identity, Palm: Step 1: Delete palm template(s)
        Identity->>Palm: POST /KZ/delete<br/>Headers: request_id: <uuid><br/>Content-Type: application/json<br/>Body: {user_id}
        alt Success
            Palm->>Palm: Remove all user's<br/>templates from Milvus
            Palm-->>Identity: {code: 0, msg: "Success"}
        else No records to delete
            Palm-->>Identity: {code: 30006, msg: "ID not found"}
            Note over Identity: Treat as idempotent success<br/>(user wasn't enrolled)
        end
    end

    rect rgb(255, 245, 230)
        Note over Identity, DB: Step 2: Anonymize user data
        Identity->>DB: Anonymize user record<br/>(hash PII, keep audit ref)
        Identity->>DB: Mark status = "deleted"
        Identity->>DB: Revoke all refresh tokens
    end

    rect rgb(230, 255, 230)
        Note over Identity, Audit: Step 3: Audit & notify
        Identity->>Audit: Log: user_deleted<br/>(user_id, method: user_request,<br/>data_deleted: [palm, profile, identities])
        Identity->>Identity: Publish UserDeleted event
        opt Webhook subscribed
            Identity->>Vertical: Send webhook: user.deleted<br/>{user_id, data_deleted}
        end
    end

    Identity-->>Vertical: {status: "deleted",<br/>deleted_at: "..."}
    Vertical->>Vertical: Clean up vertical-specific data
    Vertical-->>App: "Account deleted"
    App-->>User: Confirmation

Diagram 7.3 — Consent Withdrawal

sequenceDiagram
    autonumber
    participant User
    participant App as Mobile App
    participant Vertical as Vertical Backend
    participant Identity as Identity Platform
    participant Palm as X-Telcom BioWave Pass
    participant DB as PostgreSQL
    participant Audit as Audit Log

    Note over Identity, Audit: All audit logging is conditional<br/>on tenant audit_enabled setting

    User->>App: Withdraw consent
    App->>Vertical: POST /consent/withdraw
    Vertical->>Identity: POST /v1/consent/withdraw<br/>Authorization: Bearer <token><br/>{user_id, reason: "user_requested"}

    Identity->>DB: Get active consent
    DB-->>Identity: Consent found

    Identity->>DB: Update consent<br/>(status: withdrawn, withdrawn_at)
    Identity->>Audit: Log: consent_withdrawn<br/>(user_id, consent_id, reason)

    rect rgb(255, 245, 230)
        Note over Identity, Palm: Withdrawal triggers data cleanup
        Identity->>Palm: POST /KZ/delete<br/>Headers: request_id: <uuid><br/>Content-Type: application/json<br/>Body: {user_id}
        alt Success or no records
            Palm-->>Identity: {code: 0} or {code: 30006}
        end

        Identity->>DB: Update user<br/>(palm_enrolled: false)
        Identity->>DB: Revoke device certificates
    end

    Identity->>Identity: Publish ConsentWithdrawn event
    opt Webhook subscribed
        Identity->>Vertical: Send webhook: consent.withdrawn<br/>{user_id, consent_id,<br/>actions_taken: ["palm_deleted",<br/>"devices_revoked"]}
    end

    Identity-->>Vertical: {consent_id,<br/>status: "withdrawn",<br/>actions_taken: [...]}
    Vertical-->>App: "Consent withdrawn"
    App-->>User: "Your consent has been<br/>withdrawn and biometric<br/>data deleted"

11.3 Audit Logging

When enabled: audit_enabled: true (default)

Logs all business events with full context: IP address, user agent, actor, result, and event-specific metadata. When disabled, no audit events are recorded — tenants that don't need audit logging can turn it off.

  • Common fields on every audit event (event_id, event_type, timestamp, tenant_id, actor, ip_address, user_agent, result, metadata): defined with the audit_log schema in §5.5.
  • Full event-type catalog: §16 (Event Reference), which lists every audit event alongside its webhook counterpart (where one exists).

11.4 Data Retention

Data Type Retention
Audit logs 10 years (SAMA requirement)
Consent records 5 years
KYC data Until deleted
Palm templates Until deleted
User profiles Until deleted
Refresh tokens 30 days