Zodiac Tech CRM Docs

Data retention & privacy (GDPR)

How long the CRM keeps audit logs, login attempts, billing events and recordings — plus tenant data export, the 30-day erasure countdown and who can do what.

admin super-admin developer Medium ⏱ 20 min Updated 2026-10-05

Data retention & privacy (GDPR)

Zodiac Tech CRM stores personal data (leads, contacts, calls, recordings, messages) on behalf of the business that owns the workspace. This page is the operator's view: what is kept, for how long, which jobs delete it, and how an export or erasure request is handled.

Who this is for

  • Workspace owners and admins answering a data request.
  • Super admins and developers configuring retention on a live install.

Difficulty: Medium · Time: 20 min · Prerequisites: Admin access for the workspace-level flows; shell access for the commands.

What is kept, and for how long

Data Retention Deleted by
Audit log entries (admin and sensitive actions) 180 days (default) leadhub:purge-audit-logs, daily at 03:00
Login attempts (login_attempts) 30 days (default) leadhub:purge-audit-logs, daily at 03:00
Processed billing events (idempotency records) 90 days (default) leadhub:purge-audit-logs, daily at 03:00
Call recording audio Per the install's recording retention leadhub:purge-recordings, daily at 03:15
Leads, contacts, messages, call metadata Until the workspace deletes them or executes erasure App actions / erasure job

Retention windows are configurable per install with the environment variables LH_AUDIT_RETENTION_DAYS, LH_LOGIN_ATTEMPT_RETENTION_DAYS and LH_BILLING_EVENT_RETENTION_DAYS (see config/leadhub.php). Change them deliberately — shortening audit retention weakens your own incident forensics.

Recordings keep their metadata leadhub:purge-recordings deletes the audio file and clears the stored path, but keeps the call record itself with status = expired. The history that a call happened survives; the audio does not.

The deletion jobs (and how to dry-run them)

Always preview before purging:

php artisan leadhub:purge-audit-logs --dry-run
php artisan leadhub:purge-recordings --dry
php artisan leadhub:execute-erasure --dry-run
  • leadhub:purge-audit-logs {--days=} {--login-days=} {--billing-events-days=} {--dry-run} — overlays for a one-off run with different windows than the configured defaults.
  • leadhub:purge-recordings {--dry} — audio-only cleanup; metadata stays.
  • leadhub:execute-erasure {--dry-run} — executes scheduled tenant deletions whose 30-day countdown has passed. Runs daily at 04:00.

Tenant data export (access requests)

When someone asks "what do you hold about me / us":

  1. The workspace owner opens Admin → Data & Privacy (/admin/privacy).
  2. They press the export button. The CRM builds a ZIP of the tenant's data as JSON.
  3. The download link is valid for 24 hours (ExportTenantDataJob::DOWNLOAD_TTL_SECONDS = 86400). The build is capped at LH_EXPORT_MAX_BYTES (default 2 GB) — very large workspaces may need a narrower export.
  4. Hand the file over through a secure channel. It is not encrypted at rest by the portal; treat it as sensitive.

Erasure requests (the 30-day countdown)

The erasure flow is deliberately slow — deleting a workspace is irreversible:

  1. The owner schedules deletion on Admin → Data & Privacy (/admin/privacy). This sets deletion_requested_at and deletion_scheduled_at — a 30-day cool-off (TenantErasureService::COOL_OFF_DAYS = 30).
  2. During the countdown the subscription status becomes pending_deletion: regular sign-in is blocked, but the owner can still reach /admin/privacy (to cancel) and /admin/billing.
  3. The owner can cancel the deletion at any time during the 30 days — do this the moment they change their mind, not on day 31.
  4. After the countdown, leadhub:execute-erasure (daily, 04:00) hard-deletes the tenant's data.

Backups are not erased by the erasure job The erasure job removes live data and rows. Existing backup archives still contain a copy of the data from their date. If that matters for your compliance position, prune old backups after an erasure per your own policy — see Weekly & monthly reviews.

Who can do what

Action Who
Export own workspace data Workspace owner (Admin → Data & Privacy)
Schedule / cancel workspace deletion Workspace owner
See deleted-workspace flows run Super admin (nightly job output, log)
Run purge/erasure commands manually Developer / super admin with shell access

A suspended workspace (billing) is a different state from pending_deletion: suspension redirects users to /admin/subscription-required; pending deletion lets the owner reach only the privacy and billing pages.

A GDPR request, step by step

  1. Log the request — date, requester, what they asked for (access / erasure / both).
  2. Access: run the export (above), deliver it securely, note the delivery in your records.
  3. Erasure: if the request is for the whole workspace, schedule deletion and tell the requester the date the data will go. If it is for an individual contact within a workspace, handle it as a data change in the CRM (delete/anonymise the contact record) and record what you did.
  4. Verify: after the countdown passes, confirm the tenant is gone (the erasure job ran; check the daily job's log line and that the workspace no longer appears).
  5. Close the loop: reply to the requester with what was done and when.

Common mistakes

Mistake Why it hurts Do this instead
Erasing before the requester is sure Irreversible Explain the 30-day countdown is available — use it
Forgetting backup archives after erasure Data still exists in old zips Prune backups per policy after an erasure
Shortening audit retention to "clean up" Destroys the record of who did what Audit retention is your forensics — keep 180 days unless policy says otherwise
Emailing exports as plain attachments The ZIP is not encrypted by the portal Use a secure transfer method
Running purge commands with changed windows so they are never scheduled again Silent drift between policy and reality Keep the scheduled defaults and document any override

Technical details

  • Erasure: TenantErasureService (COOL_OFF_DAYS = 30); fields deletion_requested_at / deletion_scheduled_at; blocked sign-in via the subscription middleware chain (EnforceNotSuspended + EnforceSubscription, state from TenantSubscriptionState).
  • Export: ExportTenantDataJob (JSON in a ZIP; 24-hour link; LH_EXPORT_MAX_BYTES cap, default 2 GB).
  • Purge: PurgeAuditLogs (chunked deletes; 180/30/90-day defaults), PurgeExpiredRecordings (audio deleted, row → expired, path nulled).
  • Billing idempotency records (processed_billing_events) are kept 90 days because webhook duplicates can arrive late.

Was this page helpful?