Data Retention Policy
[PLATFORM PROVIDER] — Internal DocumentLast Updated: [DATE]
Internal operational document. It is published on the legal documentation site alongside the customer-facing policies. It consolidates the retention periods defined across the DPA, ROPA and Annex II into a single operational reference, and records, for each store of personal data, whether any mechanism actually enforces the period stated.
1. Purpose
This policy documents the data retention periods for all personal data processed by [PLATFORM PROVIDER] on behalf of customer organizations, and the mechanisms used to enforce those periods. It supports compliance with GDPR Article 5(1)(e) (storage limitation principle).
Where a period in Section 2 is stated as "No automated deletion", no scheduled job, database trigger or storage lifecycle rule removes the data on a time basis. Such data persists until one of the deletion triggers recorded in the same row occurs, each of which is an action taken by a person.
2. Retention Schedule
Retention periods are configured per organisation, and no default policy is seeded. The retention enforcement job acts only on policies that exist, so an organisation that has configured none has no data deleted by age. The retention settings screen currently submits eight data types while the API accepts five, and the API validates the whole submission before writing any of it, so every save attempt is rejected and no retention policy can be configured through the product.
Where a policy could be saved, the API accepts a period between 30 and 3,650 days, and the enforcement job independently refuses any policy below 30 days.
GAP-RET-01 · CODE — KNOWN GAP, REMEDIATION PENDING
Earlier versions of this policy presented the periods in this section as platform-wide defaults enforced by a daily job. Time-based deletion depends entirely on a per-organisation retention policy; none is seeded, and the retention settings screen cannot currently save one, so no organisation has any data deleted on a time basis. This divergence is recorded on the compliance remediation register and is under review.
2.1 Attendee Data (Postgres + Firestore)
| Data Type | Table / Collection | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| Contact record and contact PII | contacts (Postgres — email_hash only, no plaintext) + Firestore orgs/{orgId}/contacts/{emailHash}/pii/private (email address, name, telephone number) | No automated deletion — retained for the duration of the business relationship between org and [PLATFORM PROVIDER] | Attendee erasure request, or org termination (a manual operation) | Explicit delete of the contacts row, which CASCADEs to linked tables; Firestore document delete |
| Registration record | webinar_registrations (Postgres) | No automated deletion | Erasure request or org termination | CASCADE from contacts |
| Behavioral events (chat, polls, offers, attendance) | behavioral_events (Postgres) | Configurable per organisation; no policy can currently be saved, so in practice no automated deletion | Configured retention policy (once configurable), erasure request, or org termination | Delete by age where a policy exists; CASCADE from contacts |
| Email sends / delivery records | email_sends (Postgres) | No automated deletion — the enforcement job supports this table but the API rejects it as a configurable data type | Erasure request or org termination | Explicit delete by (org_id, email_hash) in the erasure pipeline — there is no foreign key to contacts, so CASCADE does not reach it |
| Push delivery records | push_sends (Postgres) | No automated deletion — the enforcement job supports this table but the API rejects it as a configurable data type | Erasure request or org termination | Explicit delete by (org_id, email_hash) |
| Consent records | consents (Postgres) + Firestore consents subcollection | No automated deletion, and no independent retention period — consent records live and die with the contact record | Erasure request or org termination | CASCADE from contacts; Firestore subcollection delete |
| Consent event history | consent_events (Postgres) | No automated deletion, and the rows survive erasure | Deletion of the organisation only | None. On erasure the contact_id is set to NULL and the row is retained — organisation, purpose, action, method, policy version and timestamp — as the Article 7(1) record that consent was given or withdrawn |
| Attendance records | attended_via and attended_at columns on webinar_registrations (Postgres) — there is no separate attendance table | No automated deletion | Erasure request or org termination | CASCADE from contacts |
| Segment membership | segment_members (Postgres) | Configurable per organisation; no policy can currently be saved, so in practice no automated deletion | Configured retention policy (once configurable), erasure request, or org termination | Delete by age where a policy exists; CASCADE from contacts |
| Lead scores / engagement data | lead_scores (Postgres) | Configurable per organisation; no policy can currently be saved, so in practice no automated deletion | Configured retention policy (once configurable), erasure request, or org termination | Delete by age where a policy exists; CASCADE from contacts |
| Share links generated by attendees | shares (Postgres) | No automated deletion | Erasure request or org termination | CASCADE from contacts |
No PostgreSQL table holds a plaintext email address, telephone number or personal name. Contacts are keyed by an SHA-256 hash of the lowercased, trimmed email address, and plaintext contact details are held only in Firestore at orgs/{orgId}/contacts/{emailHash}/pii/private.
Two exceptions apply. First, the automations engine records the outcome of each executed step as a JSON document in PostgreSQL, and for email and SMS steps that document includes the recipient's plaintext email address or telephone number; those records are removed when the contact is deleted, by cascade, but they are not otherwise pseudonymised. Second, the ai_jobs table stores the full prompt text of each AI generation, which may contain an approved attendee testimonial together with the name and company recorded on it; those rows carry no contact link and are not removed on erasure. See Section 2.8.
2.2 Device Token Data (Postgres)
| Data Type | Table | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| FCM device tokens | device_tokens | No automated deletion. device_tokens is one of the five configurable data types, keyed on updated_at, and the settings screen offers a default of 60 days; that default cannot currently be saved (see Section 2) | Configured retention policy (once configurable); an UNREGISTERED response from FCM on send; GDPR erasure | Explicit delete by (org_id, email_hash) or by (org_id, fcm_token); delete by age where a policy exists |
Enforcement: The
enforce-retentionscheduled job runs daily and deletes rows only for those organisations that hold an enabled retention policy for the data type in question. The device-token policy is keyed on theupdated_atcolumn, not on alast_updatedcolumn, which does not exist. The job runs on the scheduler's privileged database connection, which is not subject to Row Level Security.
2.3 Attribution and Marketing Data (Postgres)
| Data Type | Table | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| Attribution metadata (UTM parameters, advertising click identifiers and click platform, landing-page URL, referrer URL, country, city, device type, operating system, browser, time zone) | Columns on funnel_entries (Postgres) — there is no attribution_data table | No automated deletion — not a configurable data type | Erasure request, or deletion of the parent funnel | Explicit delete by contact_id in the erasure pipeline; CASCADE from funnels / funnel_steps. The foreign key to contacts is ON DELETE SET NULL, so cascade alone would leave the row in place. See Section 2.8 |
| Server-side conversion events (Meta Conversions API, TikTok Events API) | No local store. There is no conversion_events table; server-side conversions are transmitted to the advertising platform and are not persisted by the platform | Not applicable | Not applicable | Not applicable. Data transmitted to Meta and to TikTok passes outside [PLATFORM PROVIDER]'s control on transmission; see Section 5 |
2.4 Live Session Data (Firebase RTDB)
The Realtime Database namespace is live/{sessionId}/…. It contains no organisation segment, and there is no path of the form /orgs/{orgId}/sessions/{sessionId}/….
| Data Type | Location | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| Attendee presence and attendance | RTDB live/{sessionId}/presence, attendance, presenceTimeline, presenceSessions | No automated deletion | Manual deletion of the session or of the parent webinar | RTDB delete of live/{sessionId} |
| Chat messages and moderated messages | RTDB live/{sessionId}/stream and live/{sessionId}/removedStream | No automated deletion | Manual deletion of the session or of the parent webinar | RTDB delete of live/{sessionId} |
| Attendee display names | RTDB live/{sessionId}/actors | No automated deletion | Manual deletion of the session or of the parent webinar; erasure request (per actor) | RTDB delete of live/{sessionId}; per-actor delete in the erasure pipeline |
| Poll data | RTDB live/{sessionId}/polls, pollVotes, pollCounts | No automated deletion | Manual deletion of the session or of the parent webinar | RTDB delete of live/{sessionId} |
| Reactions, offer clicks and feedback responses | RTDB live/{sessionId}/reactions, reactionsByActor, offerClicks, feedbackResponses | No automated deletion | Manual deletion of the session or of the parent webinar; erasure request (per actor) | RTDB delete of live/{sessionId}; per-actor delete in the erasure pipeline |
| Identity cache (anonymous actor identifier mapped to the hash of the attendee's email address) | RTDB live/{sessionId}/identityCache | No automated deletion | Manual deletion of the session or of the parent webinar; erasure request (per actor) | RTDB delete of live/{sessionId}; per-actor delete in the erasure pipeline |
| Per-actor synchronisation markers and per-actor message counts | RTDB live/{sessionId}/synced and live/{sessionId}/actorMessageCounts | No automated deletion, and not reached by the per-actor erasure routine | Manual deletion of the session or of the parent webinar | RTDB delete of live/{sessionId} |
| Session stats (counters) | RTDB live/{sessionId}/stats | No automated deletion | Manual deletion of the session or of the parent webinar | RTDB delete of live/{sessionId} |
Live-session data in the Realtime Database is retained indefinitely. There is no scheduled cleanup function: the Cloud Functions codebase exports ten functions, of which nine are database triggers and one is a minutely presence reconciler, and none deletes session data. Moving a session to replay or archived status does not prune it either — the lifecycle job writes a statistics snapshot to Firestore and performs no deletion in the Realtime Database. The only operations that remove a live-session node are the manual deletion of a session and the manual deletion of the parent webinar. Chat message text, attendee display names, presence and attendance records, poll votes, reactions, offer clicks and feedback responses therefore persist for the lifetime of the webinar record.
The statistics snapshot written to the Firestore session document at the end of a session comprises counters — peak concurrent viewers, message count, author count, poll-vote count, offer clicks and unique offer clicks — together with a presence timeline and the time at which the offer was activated. No chat message content is archived to Firestore, and there is no per-organisation archive-retention setting.
GAP-RET-02 · CODE — KNOWN GAP, REMEDIATION PENDING
Earlier versions of this policy stated that live-session data is removed by a scheduled cleanup function shortly after a session ends. No such function exists: the Cloud Functions codebase exports ten functions, of which nine are database triggers and one is a minutely presence reconciler, and none of them deletes session data. Live-session records — chat message text, attendee display names, presence, attendance, poll votes, reactions and feedback — are removed only when a staff member deletes the session or the parent webinar. This divergence is recorded on the compliance remediation register and is under review.
Confidentiality note. Access to live-session data in the Realtime Database is authorised by the role claim in the caller's authentication token alone. The security rules contain no organisation identifier and make no comparison between the caller's organisation and the organisation that owns the session, so an authenticated dashboard user holding any of the admin, moderator, marketer or staff roles — in any organisation on the platform — can read any live session's data given only its session identifier. This includes the chat stream, the moderated-message stream, presence, attendance, presence timelines, per-actor message counts, offer clicks, feedback responses and the identity cache that maps an attendee's anonymous actor identifier to the hash of their email address. Separately, the chat stream, the actor directory (which holds attendee display names), the banned and muted actor lists, the feedback definition and the tracked-link table are readable by any authenticated user, which includes every anonymous attendee of every session. All organisations share a single Firebase project per environment, so a single rule set governs the whole platform. Indefinite retention and unrestricted readability compound: the data described in the table above remains readable on those terms for as long as it is retained.
2.5 Configuration and Org Data (Firestore)
| Data Type | Collection | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| Org configuration | orgs/{orgId} | No automated deletion — retained for the duration of the subscription | Org termination, performed manually (see Section 3.1) | Firestore document and subcollection delete, executed manually |
| Webinar and session configuration | orgs/{orgId}/webinars/{webinarId} and orgs/{orgId}/webinars/{webinarId}/sessions/{sessionId} | No automated deletion | Manual deletion of the webinar or session; org termination | Firestore subcollection delete |
| Attendee contact docs (PII) | orgs/{orgId}/contacts/{emailHash} and its pii/private and consents subcollections | No automated deletion | Erasure request or org termination | Firestore document delete |
| Registration documents | orgs/{orgId}/webinars/{webinarId}/sessions/{sessionId}/registrations/{registrationId} | No automated deletion | Erasure request; manual deletion of the parent webinar or session | Firestore document delete, per registration, in the erasure pipeline |
No time-to-live policy is configured on any Firestore collection. Registration documents and contact PII documents are removed only by an erasure request or by the manual deletion of the parent webinar or session.
2.6 Authentication Data
| Data Type | Location | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| Firebase Anonymous Auth UID (web attendees) | Firebase Auth (project-level) | No automated expiry. Retained until a member of staff runs the manual flush endpoint, whose default cutoff is accounts created more than 7 days ago | Manual staff-initiated flush; GDPR erasure | Batch delete of Auth accounts holding no identity provider; per-actor delete in the erasure pipeline |
| Firebase Auth UID (mobile app users) | Firebase Auth + Firestore contact doc field | No automated deletion — retained for the duration of the business relationship | Erasure request or org termination | Firebase Auth delete + Firestore field delete |
| Firebase Auth JWT (mobile app, on-device) | iOS Keychain / Android EncryptedSharedPreferences | 1 hour (auto-refreshed during active use) | App uninstall, logout, or token expiry | OS secure storage cleanup / Firebase Auth token revocation |
| Email one-time verification codes (holds the plaintext email address alongside the code) | Firestore verification_codes/{emailHash} | 10 minutes | Successful use, expiry, or 3 failed attempts | Firestore document delete on use, on expiry check, or on the third failed attempt |
| Magic-link records (holds the plaintext email address) | Firestore magic_links/{oobCode} | Deleted on first use; otherwise no automated deletion | Use of the link; otherwise the manual staff-initiated flush described above | Firestore document delete |
The anonymous Auth UID is not confined to Firebase Auth: the same identifier is the actor key at live/{sessionId}/actors/{actorId}, is stored in the actor_id column of webinar_registrations, and is linked to the contact's email hash at live/{sessionId}/identityCache/{actorId}. Only one actor identifier is stored per registration, so where an attendee joins from more than one device, or re-joins under a new anonymous identity, the identifiers not recorded on the registration are not reached by the per-actor erasure routine.
2.7 Audit Logs
| Data Type | Location | Retention Period | Notes |
|---|---|---|---|
| API write operation audit logs | PostgreSQL audit_log table | No automated deletion | Retained indefinitely for accountability under Art 5(2); not subject to erasure requests (legal basis: legitimate interest in a security audit trail). Entries record the acting user's IP address and user agent, and entries created by the data-subject rights workflow record the subject's email hash in their metadata |
| GDPR request logs | PostgreSQL gdpr_requests table | No automated deletion | Retained indefinitely as the record of erasure and access actions. Holds the requesting subject's email hash |
| Application and security event logs | Structured application logs written to standard output and captured by the hosting provider | ⚠️ Not established. There is no separate incident-log store; the hosting provider's log retention period is not evidenced in [PLATFORM PROVIDER]'s records and must be confirmed with the provider | See the monitoring statement below |
The audit log and the data-subject request register are not among the data types the retention job recognises, and nothing else in the platform deletes from them, so both grow without limit. The audit log records the acting user's IP address and user agent, and entries created by the data-subject rights workflow record the subject's email hash in their metadata, so an erased subject's email hash remains in the audit log after erasure. The application database role holds only select and insert privileges on the audit log, which makes it append-only for the application but also means it cannot be purged through the application's connection.
Monitoring and alerting. The platform emits structured application logs to standard output, which the hosting provider retains and makes searchable, and exposes an administrator-only system-health endpoint reporting request counters and database read counters. Those counters are held in process memory and reset on every restart or deployment. There is no error-tracking service, no on-call paging integration and no automated alerting: incident detection depends on a person observing logs, the health endpoint or a customer report.
GAP-RET-05 · CODE — KNOWN GAP, REMEDIATION PENDING
This policy previously stated a two-year retention period for the audit log, a three-year period for the data-subject request register, and a one-year period for security incident logs held in an internal logging service. None of those three tables or stores is recognised by the retention job, no such logging service exists, and no automated alerting is in place; the audit log and the request register are retained indefinitely and cannot be purged through the application's database role. This divergence is recorded on the compliance remediation register and is under review.
2.8 Funnels, Automations, AI Generation, SMS and Broadcasts (Postgres)
| Data Type | Table | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| Funnel entries (attribution, device, approximate location, webhook payloads, purchase metadata) | funnel_entries | No automated deletion — not a configurable data type | Erasure request (by contact) or deletion of the parent funnel | Explicit delete by contact_id in the erasure pipeline; CASCADE from funnels / funnel_steps |
| Funnel page views (persistent pseudonymous visitor hash of IP + user agent) | funnel_page_views | No automated deletion — not a configurable data type | Deletion of the parent funnel or step only | CASCADE from funnels / funnel_steps. Not reached by erasure — no link to a contact record |
| Automation enrolments (contact, status, triggering event) | automation_enrollments | No automated deletion — not a configurable data type | Erasure request or deletion of the automation | CASCADE from contacts; CASCADE from automations |
| Automation step executions (plaintext email address / telephone number in the result document) | automation_step_executions | No automated deletion — not a configurable data type | Erasure request or deletion of the automation | CASCADE from automation_enrollments only |
| AI generation jobs (system prompt, user prompt, model output — may contain attendee testimonial name and quote) | ai_jobs | No automated deletion — not a configurable data type | Deletion of the organisation only | No deletion path. Not reached by erasure — rows are keyed to an entity and a staff user, not to a contact record |
| SMS delivery records | sms_sends | No automated deletion — the enforcement job supports this table but the API rejects it as a configurable data type, so no policy can be saved | Would be a configured retention policy, once configurable | Delete by age (currently unreachable). Not reached by erasure — keyed by email_hash with no foreign key to contacts; absent from the Art 15 export |
| Segment broadcast recipient log | segment_broadcast_sends | No automated deletion — not a configurable data type | Deletion of the organisation only | No deletion path. Not reached by erasure — keyed by email_hash with no foreign key to contacts; absent from the Art 15 export |
| Segment broadcast content (subject, title, body, call to action, author) | segment_broadcasts | No automated deletion — not a configurable data type | Deletion of the organisation only | No deletion path |
| Inbound webhook activity log (per-call metadata, which for e-commerce and form sources may carry recipient-level payload data) | webhook_events | No automated deletion — not a configurable data type | Deletion of the organisation only | No deletion path. Not reached by erasure — rows carry no link to a contact record |
GAP-RET-01 · CODE — KNOWN GAP, REMEDIATION PENDING
The funnels, automations, AI-generation, SMS and segment-broadcast subsystems were absent from this policy, which described itself as consolidating every retention period across the corpus. None of the tables above is a configurable data type, so none is subject to any automated retention period, and the SMS delivery log, the segment-broadcast recipient log, the funnel page-view table, the AI generation-job table and the webhook activity log are not reached by the Article 17 erasure pipeline at all. This divergence is recorded on the compliance remediation register and is under review.
2.9 Object Storage (Firebase Cloud Storage)
| Data Type | Location | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| Webinar, page, offer and page-template assets; the organisation media library; branding assets; staff assets — marketing imagery which may depict identifiable individuals | Cloud Storage prefixes orgs/{orgId}/webinars/, pages/, offers/, media/, page_templates/, branding/ and staff/ | No automated deletion — no storage lifecycle rule is configured and object storage is not a configurable data type | Manual deletion of the parent webinar, page, offer, template, media item or upload | Delete of the individual object, or of the entity's storage prefix, performed by the handler that deletes the parent entity |
Object storage is not reached by the Article 17 erasure pipeline: the erasure routine performs no Cloud Storage operation, so an object depicting or naming a data subject is removed only when a member of staff deletes the entity that owns it.
The Cloud Storage security rules grant unconditional public read access to all seven organisation-scoped paths — webinars/, pages/, offers/, media/, page_templates/, branding/ and staff/ — so any object stored under an organisation's prefix is readable by anyone who knows or can guess its path, without authentication. Write access on those same paths requires only that the caller be authenticated; the organisation identifier in the path is never compared against the caller's token, so an authenticated user of one organisation can overwrite another organisation's branding, media, page templates and landing-page files. A final catch-all rule grants read and write access to every remaining object in the bucket to any authenticated user, which includes every anonymous attendee.
GAP-ISO-03 · CONFIG — KNOWN GAP, REMEDIATION PENDING
Object storage was absent from this policy, which described itself as consolidating every retention period across the corpus, and was described elsewhere in the corpus only as a store of landing-page HTML. It also holds the organisation media library, branding assets, page templates and staff assets, no lifecycle rule deletes any of it, and the Article 17 erasure pipeline performs no storage operation, so stored imagery survives an erasure. This divergence is recorded on the compliance remediation register and is under review.
2.10 Transient Copies of Contact PII (API process memory)
The API holds an in-process cache of plaintext contact details (email address, name and telephone number), keyed by organisation and email hash, to avoid repeated Firestore reads. The cache has no expiry, and entries are invalidated only within the process that performs the write. Where more than one API instance is running, a correction or a deletion applied on one instance does not evict the copy held by another.
In consequence, a rectification under Article 16 and an erasure under Article 17 are not effective across the whole API tier until each process is restarted or redeployed. The size of the cache is reported on the administrator-only system-health endpoint.
2.11 Backups
Backups are operated by the hosting providers rather than by the application, and no backup configuration is defined in [PLATFORM PROVIDER]'s codebase.
| Data Type | Location | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| PostgreSQL backups | Hosting provider (Railway Corp.) | Annex II and the ROPA both state 30 days. ⚠️ The provider's actual configuration is not evidenced in [PLATFORM PROVIDER]'s records and must be confirmed | Ageing out of the backup rotation | None available to the application — backups are immutable and cannot be edited to remove an individual record |
| Firestore backups | Google LLC | The ROPA states 7 days. ⚠️ The configured schedule and retention are not evidenced in [PLATFORM PROVIDER]'s records and must be confirmed | Ageing out of the backup rotation | None available to the application |
| Realtime Database | Google LLC | Annex II records that live-session data is not backed up | Not applicable | Not applicable |
Personal data erased under Article 17 remains present in any backup taken before the erasure until that backup ages out. The 30-day figure above and the 90-day figure at DPA Section 10.1 are different measures and are not in conflict: 30 days is the rotation period after which a given backup no longer exists, and 90 days is the outer deadline by which [PLATFORM PROVIDER] undertakes that data deleted on termination has aged out of every backup.
2.12 Data Exported Out of the Platform
Staff can export the members of a saved segment from the dashboard. The export resolves each member's plaintext email address and name from Firestore and returns them for download; it is shaped for manual import into the email provider.
| Data Type | Location | Retention Period | Trigger for Deletion | Deletion Method |
|---|---|---|---|---|
| Segment contact exports (plaintext email address and name per segment member) | Files held by the Customer outside the platform | Not controlled by [PLATFORM PROVIDER]. No retention job and no erasure pipeline can reach an exported file | Determined by the Customer as controller | Deletion by the Customer |
The export does not require an email_marketing consent, and where the requesting member of staff sets the include-flagged option it also returns contacts suppressed for bounce, spam complaint or global opt-out. Contacts who have unsubscribed appear in the default export, because an unsubscribe withdraws the marketing consent without setting the suppression flag. Only contacts subject to a GDPR Article 18 processing restriction are excluded in every case. Each export is recorded in the audit log as exported_segment_contacts, which is the compensating control; the Customer, as controller, remains responsible for the retention, security and deletion of the exported file.
3. Enforcement Mechanisms
3.1 Automated Scheduled Jobs
| Job | Schedule | Action |
|---|---|---|
enforce-retention | Daily (02:00 by default; operator-configurable) | Reads every row of retention_policies and, for each, deletes rows older than the configured period from one of behavioral_events, segment_members, device_tokens, lead_scores, email_sends, push_sends, sms_sends or scheduler_job_runs. It acts only on policies that exist, and no policy can currently be created (see Section 2). Runs on the scheduler's privileged database connection, which is not subject to Row Level Security |
Where an enabled policy would delete more than ten thousand rows in a single run, the job records a warning and deletes nothing, so a table whose backlog has crossed that threshold is not brought back within its retention period by subsequent runs. A policy configured below the 30-day minimum is skipped with a warning and is not counted as a failure.
No other scheduled job deletes personal data. In particular:
- There is no Cloud Function or scheduled job that removes live-session data from the Realtime Database. Session data is deleted only by the manual deletion of a session or of its parent webinar (see Section 2.4).
- There is no org-termination cleanup job. No organisation status field drives deletion, and no grace-period timer exists. Deletion on termination — the obligation at DPA Section 10.1 — is carried out manually by engineering.
- The flush of anonymous Firebase Auth accounts and of stale magic-link documents is a manual, administrator-gated endpoint, not a scheduled job (see Section 2.6).
GAP-RET-02 · CODE — KNOWN GAP, REMEDIATION PENDING
This policy previously listed two further scheduled jobs, cleanup-rtdb-sessions and org-termination-cleanup. Neither exists: the platform registers eight scheduled jobs, of which only enforce-retention deletes personal data, and the Cloud Functions codebase contains no cleanup function. Deletion of live-session data and deletion on termination of an organisation are therefore manual operations with no automated timer behind them, including for the 30-day commitment at DPA Section 10.1. This divergence is recorded on the compliance remediation register and is under review.
3.2 Event-Triggered Deletion
| Event | Action |
|---|---|
FCM returns UNREGISTERED on push send | Delete the specific device_tokens row immediately |
| A member of staff processes a GDPR erasure request from the dashboard | Execute the deletion pipeline described in Section 3.3 |
| An attendee whose contact record is already flagged for a hard bounce or a rejected address uses the self-service deletion path | Create a GDPR deletion request in pending status. Nothing processes it automatically; a member of staff must action it. The path returns 403 for any contact not so flagged |
| Customer terminates subscription | Manual deletion by engineering within the DPA Section 10.1 window. There is no grace-period timer and no automated cleanup job |
Withdrawing a consent does not trigger an erasure. data_processing and tos_acceptance cannot be withdrawn at all: they are granted at registration, the preferences endpoint strips them from any submission, and a withdrawal arriving through the consent-sync path is recorded as skipped. No code path creates a GDPR request on the withdrawal of any consent.
GAP-RET-10 · CODE — KNOWN GAP, REMEDIATION PENDING
This policy previously stated that an attendee's withdrawal of the data_processing consent triggers the GDPR erasure pipeline. That consent cannot be withdrawn through any interface, and no consent withdrawal creates an erasure request. Erasure is initiated by a member of staff, or by the narrow self-service path available only to contacts already flagged for a hard bounce or a rejected address, which creates a request that a member of staff must then process. This divergence is recorded on the compliance remediation register and is under review.
3.3 Manual Deletion (DSAR)
GDPR erasure requests processed via the dashboard or the DSAR procedure (dsar-procedure.md) execute, in order:
- PostgreSQL: explicit deletes of
email_sends,push_sendsanddevice_tokensmatched on (org_id, email_hash); nullification of share referrals onfunnel_entries; an explicit delete offunnel_entriesmatched oncontact_id; then deletion of thecontactsrow, which CASCADEs tobehavioral_events,segment_members,consents,webinar_registrations,lead_scores,sharesandautomation_enrollments(and, through the enrolment, toautomation_step_executions). - Firestore: deletion of each registration document, of the contact consents subcollection, of the contact PII document and of the contact document itself.
- Firebase Auth: deletion of the anonymous account recorded against each registration and of the non-anonymous account referenced on the contact document.
- Realtime Database: per-actor deletion, for each registration that carries an actor identifier, of attendance, presence, actor display name, rate limits, poll votes, reactions, offer clicks, feedback responses, presence sessions, identity-cache and consent-cache entries, banned and muted actor entries, chat and moderated-chat messages and reported messages.
- Email provider: the contact's plaintext email address, read from Firestore before deletion, is submitted to the email provider as a permanent suppression (see Section 5).
- Confirmation logged in the
gdpr_requeststable.
What the pipeline does not reach. Two tables holding personal data keyed by email hash are not reached by the erasure routine and have no foreign key to the contact record, so they are not removed by cascade either: the SMS delivery log and the segment-broadcast recipient log. A per-person record of SMS messages sent — email hash, webinar and session, template type, tracking code, provider message identifier, delivery status and delivery and click timestamps — therefore persists after an Article 17 erasure, and neither table is included in the Article 15 data export.
The following also hold personal data of the data subject and survive an erasure:
consent_events— the Article 7(1) consent history. Thecontact_idis set to NULL; the organisation, purpose, action, method, policy version and timestamp are retained.audit_logandgdpr_requests— retained indefinitely for accountability; audit metadata written by the rights workflow includes the subject's email hash.ai_jobs,funnel_page_viewsandwebhook_events— no link to a contact record, so neither the explicit deletes nor cascade reaches them.- Realtime Database
live/{sessionId}/synced/…andlive/{sessionId}/actorMessageCounts/{actorId}— not covered by the per-actor deletion routine. - Any live-session data written under an actor identifier other than the one recorded on the registration — a second device, or a re-join under a new anonymous identity — including chat messages and display names.
- Cloud Storage objects (Section 2.9), which the pipeline never touches.
- The in-process PII cache on any API instance other than the one that executed the deletion, until that process restarts (Section 2.10).
- The contact's plaintext email address at the email provider, which is retained on a permanent suppression list by design (Section 5).
GAP-RET-03 · CODE — KNOWN GAP, REMEDIATION PENDING
This policy previously described the erasure pipeline as removing all personal data for the data subject across all tables and collections. The pipeline reaches the stores enumerated above, but the SMS delivery log, the segment-broadcast recipient log, the AI generation-job table, the funnel page-view table, the webhook activity log, Cloud Storage objects and two per-actor Realtime Database nodes survive it, and live-session data written under a second actor identifier is not reached at all. This divergence is recorded on the compliance remediation register and is under review.
4. Data Subject Erasure vs. Org Termination
| Scenario | What Is Deleted | What Is Retained |
|---|---|---|
| Attendee erasure request | The stores enumerated at Section 3.3, steps 1 to 4 | The stores enumerated at Section 3.3 under "What the pipeline does not reach", together with aggregated session statistics that are not tied to an individual |
| Org subscription termination | All org data (contacts, webinars, registrations, behavioral events, device tokens, push records), deleted manually by engineering — there is no automated cleanup job and no grace-period timer | Audit logs and the data-subject request register, which no mechanism purges |
| [PLATFORM PROVIDER] platform shutdown | All customer data per DPA Section 10 (Deletion and Return of Personal Data): deletion from production systems within 30 days, and from backups within 90 days as they age out; or return in machine-readable form at Customer's election | None — full deletion or return to customer, subject to the legal retention exception at DPA Section 10.2 |
5. Sub-Processor Retention
[PLATFORM PROVIDER] engages eleven sub-processors, listed in Annex III. Deletion propagates to them as follows.
| Sub-Processor | Data Covered | Deletion Mechanism |
|---|---|---|
| Google LLC (Firestore, RTDB, Firebase Auth, Cloud Storage) | Auth UIDs, Firestore documents, RTDB nodes, stored objects | Firebase Admin SDK delete calls in the deletion pipeline for Auth, Firestore and RTDB. Cloud Storage objects are not deleted by the pipeline (Section 2.9) |
| Railway Corp. (PostgreSQL, application hosting) | All Postgres tables | Direct database operations in the deletion pipeline, subject to the exclusions at Section 3.3. Backups age out (Section 2.11) |
| NewsmanApp SRL (transactional and marketing email) | Subscriber records synchronised from the platform, delivery logs, and view, click, bounce, complaint and unsubscribe events | Automatic on erasure — but as a suppression, not a deletion; see the note below |
| Meta Platforms, Inc. (Meta Pixel, Conversions API) | Hashed identifiers, IP address, user agent and page URL transmitted at registration and on landing-page visits | None. Transmission is one-way and fire-and-forget; the platform holds no local event store and has no deletion or suppression call to Meta |
| Google LLC (Google Analytics, Google Ads) | Client-side analytics and advertising identifiers | None. No server-side deletion mechanism is implemented; deletion is a matter between the Customer and Google |
| TikTok Pte. Ltd. (TikTok Pixel, Events API) | Hashed identifiers plus raw IP address, raw user agent and click identifiers transmitted on every registration | None. Transmission is one-way and fire-and-forget; no deletion or suppression call is implemented |
| LinkedIn Corporation | Not applicable — the integration is configured for in the data model but is not active | Not applicable |
| Cloudflare, Inc. (reverse proxy, CDN, Turnstile) | Request metadata for every attendee request, including registration submissions, and IP addresses submitted for Turnstile verification | None available to the application. Retention at the provider is governed by the Customer's or [PLATFORM PROVIDER]'s Cloudflare account settings |
| Microsoft Corporation (Microsoft Clarity), contracted for EU customers via Microsoft Ireland Operations Limited (MIOL) | Session-replay and analytics data for visitors who accept analytics cookies | None implemented in the platform. The consent script opts the visitor out on withdrawal; deletion is a matter between the Customer and Microsoft |
| SendSMS.ro (transactional SMS submission) | Recipient telephone number in plaintext and the full message body, submitted as query-string parameters and therefore to be assumed present in the provider's request logs | None implemented. No suppression or deletion call is made on erasure, and the local sms_sends log is itself not deleted (Section 2.8) |
| Anthropic PBC (Claude API) | Generation prompts and outputs, which may embed an approved attendee testimonial — the author's display name, quote, company and rating — and staff-uploaded reference images | None implemented. The local ai_jobs record is not reached by erasure (Section 2.8), and no deletion call is made to the provider |
Note on the email provider. Erasure at the email provider is a suppression, not a deletion. On completing an erasure the platform submits the contact's email address to the email provider with a permanently-rejected status, so that no future message can be delivered to it. By design this keeps the plaintext email address on file at the provider indefinitely. The call is made automatically by the platform as the final step of the erasure pipeline; it does not require any action by the Customer.
Note on Customer-provided integrations. Where an integration runs on the Customer's own account — the email provider, the Customer's Firebase Cloud Messaging project, and the SMS provider — the Customer holds the contract with that provider and remains responsible for any deletion the platform does not perform on its behalf.
GAP-RET-06 · CODE — KNOWN GAP, REMEDIATION PENDING
This policy previously stated that [PLATFORM PROVIDER] cannot call the email provider on the Customer's behalf and that the Customer must trigger the deletion of a contact there. The platform in fact calls the provider automatically on every erasure, and the call places the data subject's plaintext email address on a permanent suppression list rather than deleting it, so the address is retained at the provider indefinitely. The section also listed three sub-processors where eleven are engaged, and named two of them by an incorrect legal entity. This divergence is recorded on the compliance remediation register; the classification is under review.
6. Legal Holds
If [PLATFORM PROVIDER] receives a valid legal hold order (e.g., court order, regulatory investigation), the data subject to the hold is preserved by organisational measures only: engineering disables the affected retention policy manually, and the request is refused at the data-subject request triage step. Legal holds are reviewed by legal counsel. Data is released from hold as soon as the legal basis expires.
There is no legal-hold flag in the data model. Neither the retention enforcement job nor the erasure pipeline evaluates any hold condition, so neither would stop of its own accord if a hold were in force.
GAP-RET-07 · CODE — KNOWN GAP, REMEDIATION PENDING
This policy previously stated that data under a legal hold is excluded from the automated retention schedule and that holds are tracked in an internal register. No hold flag exists on any table, the retention job deletes on organisation and timestamp alone, and the erasure pipeline performs no hold check, so the exclusion is a manual practice rather than a technical control. This divergence is recorded on the compliance remediation register and is under review.
7. Review Cycle
This policy must be reviewed:
- Annually
- When new data types or processing activities are added
- When retention periods are changed (requires DPA amendment and customer notification)
- After a DSAR reveals a retention gap
All four triggers have fired since this policy was last substantively revised. The processing activities introduced in the interval — the funnels subsystem and its page-view tracking, the automations engine, AI-assisted content generation, SMS notifications, segment broadcasts, inbound e-commerce order webhooks, the per-actor Realtime Database markers and the in-process PII cache — are now recorded at Section 2.4 and at Sections 2.8 to 2.10. This policy carries no recorded review date against which the annual cycle can be measured; recording one is a precondition of the next review.
Document Version: 1.0 (DRAFT) Prepared by: [Legal Team / Engineering] Review Status: ⚠️ REQUIRES LEGAL REVIEW — Retention periods must be validated against applicable law and customer agreements before enforcement.
Last Updated: [DATE] Next Review: [DATE + 1 year]