Data Deletion
GDPR/CCPA deletion requests arrive from the app, from MX through the backoffice, and from Zendesk "Delete My Data" tickets. Every request is recorded as a Data deletion request row and produces a message for the janitor, which performs the vendor scrub and writes the final status. The janitor also runs the post-cancellation cleanup queued by account closure and bans.
Sources and Scopes
| Source | Operation | Scope | Response |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
From the ticket tags — see Zendesk Webhook |
|
| Scope | Effect |
|---|---|
|
Vendor scrub only (Vendor Signals). The account stays open. |
|
The membership is scheduled for cancellation ( |
Processing a request with a known user: write the pending row (requested_at = now), schedule the cancellation for full, then queue a marketing cleanup message carrying the user id, email, source and requested_at. Metric user.data_deletion with scope:, source: and user_found: tags.
Zendesk Webhook
-
Scope from the ticket tags:
delete_all_floatme_data→full, elsedelete_marketing_data→marketing; neither is400 "no recognizable deletion tag".requester_emailis required (400). -
Look the requester up by email, then by phone when exactly one user matches.
-
A ticket already
solvedorclosedis skipped (204). -
Dispatch:
| Scope | Account | Action |
|---|---|---|
|
found |
Process as above; post the marketing-data comment; solve the ticket |
|
none |
Anonymous marketing scrub (below); solve the ticket with the marketing-data comment |
|
found |
Process as above; solve the ticket with the cancellation comment, naming the billing-cycle end date from the Subscription Service when available |
|
none |
Anonymous marketing scrub; solve the ticket with the "no account found, resubmit" comment and add the |
An anonymous scrub writes the row under USERS#anon-<sha256(email)[:16]> with scope marketing and queues a cleanup message with an empty user id and the email. Zendesk comment and close failures are logged only; the solved ticket is assigned to the Ollie service account. Comment texts live in pkg/zendesk/deletion_responses.go.
Janitor
The cleanup Lambda consumes {env}-user-service-cleanup. A message that fails processing is reported as a batch item failure and retried; after 5 receives it lands on the DLQ.
Message
| Field | Meaning |
|---|---|
|
Empty for anonymous scrubs |
|
|
|
Present for anonymous scrubs |
|
Locates the deletion row to update; zero when no row exists |
|
|
A body that is a bare user_id string (sent by account closure and bans) is treated as {"user_id": …, "scope": "full"}.
Source: pkg/cleanup/cleanup.go
Pipeline
Steps run in order; each failure is collected and the remaining steps still run.
-
fullscope only: remove every active Plaid item in the Transactions Service (a412means the user has an active float and the removal stops silently), block the Auth0 account, schedule Array cleanup in the Entitlements Service (201or404counts as success). -
Vendor signals, only when
sourceisapp,websiteorbackoffice. -
When
requested_atis set, update the deletion row:statuscompleted, orpartial_failurewhen any step or any vendor call returned an error,completed_at, andvendor_results. A failed status write is logged and does not retry the message.
Vendor Signals
Skipped entirely, with every vendor recorded as skipped: gdpr_signals_disabled, when the GrowthBook flag gdpr_signals_enabled is off (Feature Flags). With a user_id the email and AppsFlyer id come from the user record; without one only the email is available.
| Vendor | Call | Recorded result |
|---|---|---|
Iterable |
Forget user by email |
|
AppsFlyer |
Request deletion by |
|
Segment |
Suppress and delete by |
|
Only error: results count towards partial_failure.