Skip to content

GDPR anonymize

TwistCue’s anonymize operation:

  • Nulls PII on users: email, phone_e164, display_name, avatar_url, auth_provider (set to anonymized).
  • Nulls PII on devices: ip_last, fingerprint, device_model.
  • Marks users.status = 'deleted': full API lockout.
  • Keeps id, country_code, created_at, and the entire coin ledger + purchase history (money doesn’t disappear; regulators need this).
  • Support notes about this user are kept (audit trail): the note author’s identity is preserved.
  • Writes an AuditLog entry with action='user.anonymize'.

Anonymization is approval-gated by default:

  1. Initiator (usually a compliance-flagged admin) calls POST /admin/users/:id/anonymize with a reason.
  2. Service creates an admin_approval row with action_type='user.anonymize', status=pending.
  3. A second admin with users:anonymize reviews and approves.
  4. On approval, the service executes the destructive nulling in a transaction.
  5. Audit entries land on both the approval decision and the execute.
  • Reason: required, 5–500 chars. Ticket URL or written-request reference goes here.
DataAfter anonymize
User rowExists, PII nulled, status=deleted
Email / phone / name / avatarNulled
Country + languageKept (aggregate stats need it)
DevicesKept, but IPs/fingerprints nulled
Coin ledgerKept: legal + reconciliation
Purchase historyKept: accounting + refund windows
SubscriptionsKept, but auto-renew must be canceled separately
CommentsAuthor changed to “Deleted user”, body kept unless the request also asks for content deletion
Support notesKept
Audit log entriesKept forever
  • Anonymize does NOT cancel subscriptions. The billing rail keeps charging. Cancel via the appropriate provider (App Store, Google Play, dLocal, card MID) before anonymizing, or the user will keep being billed and you’ll owe refunds.
  • Anonymize is one-way. There is no un-anonymize. If you’re wrong, the data is gone.
  • The user’s coin balance still exists: just no one can log in as them. In practice this is fine (regulators care about PII, not spend numbers).
  • New signup on the same email works fine: the anonymized row is de-linked from that email.
Terminal window
# Step 1: Initiator requests
curl -X POST https://api.twistcue.local/v1/admin/users/{userId}/anonymize \
-H "Authorization: Bearer $INITIATOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason": "GDPR request, ticket #7841"}'
# → {"kind": "approval_pending", "approvalId": "..."}
# Step 2: Second admin (different admin_id) approves
curl -X POST https://api.twistcue.local/v1/admin/approvals/{approvalId}/approve \
-H "Authorization: Bearer $APPROVER_TOKEN" \
-d '{}'
# → user is anonymized inside the approval-execution transaction.

Full details in API, Users and API, System (for the approvals endpoint).