Data Retention & Deletion Policy — G9 Returns
Version: 1.0 · Companion to the G9 Returns DPA v1.0 and Privacy Policy.
⚠️ Template, not legal advice. This is a starting template grounded in how G9 Returns actually processes data. Have it reviewed by qualified legal counsel before relying on it, and align it with your master Terms of Service, Privacy Policy, and Data Processing Agreement.
This policy describes how long G9 AS ("we", the Processor) retains personal data processed through the G9 Returns service ("Service"), what triggers deletion, and the exceptions. It reflects Article 5(1)(e) GDPR (storage limitation) and the Norwegian Personal Data Act, and is consistent with Annex II (Retention) of the G9 Returns DPA.
1. Principle: keep only as long as needed
We retain personal data only for as long as necessary to provide the Service to the merchant (the Controller) — namely, to let a shopper request a return or exchange, verify order ownership, issue a Posten/Bring return label, and process the refund/exchange — plus any period required by law. When a retention purpose ends, the data is deleted or irreversibly anonymised on the cycle described below. We do not retain personal data "just in case", and we do not repurpose it.
By design, we minimise what we hold in the first place:
- A pseudonymised customer reference (Shopify customer GID or a salted hash) is stored instead of raw customer identifiers.
- PII is masked/pseudonymised before any AI processing; the AI never receives raw customer identifiers beyond what is functionally required.
- The append-only audit /
InsightEventlog stores no raw PII. - Per-tenant credentials are encrypted with AES-256-GCM (AAD-bound to the tenant).
2. Retention by data category
| Data category | What it contains | Retention | Notes |
|---|---|---|---|
| Return & exchange records | Order identifiers, item/return details, return state (requested → … → restocked/rejected), label/QR references | For the life of the merchant's Service agreement, then deleted at end of Service; individual records deleted on a matching customers/redact (see §3) | Operational data needed to process and reconcile the return/exchange |
| Shopper PII held against a return | Shopper email and shipping postcode/address (used only to verify order ownership) | Same trigger as the parent return record; deleted on customers/redact or end of Service | Minimal verification data; never used to build a marketing profile |
| Pseudonymised customer reference | Shopify customer GID or salted hash — not raw identifiers | Deleted together with the related return data on customers/redact or end of Service | This is the join key the */redact flows resolve against |
Audit / InsightEvent log | Append-only, pseudonymised record of processing/agent actions; no raw PII | Retained for operational integrity, security, and audit; automatically pruned after 24 months on the log's own rolling cycle, and purged immediately on tenant purge / end of Service | Append-only by design; carries no raw customer identifiers, so it is not itself the subject of a per-customer redact |
| Encrypted tenant credentials | Per-tenant API keys/secrets (Bring, FrontSystems, Shopify, etc.), encrypted AES-256-GCM with tenant-bound AAD | Held only while the tenant is active; deleted/revoked on tenant offboarding (shop/redact) or end of Service | Encryption key separation means a purged tenant's ciphertext is unusable even before physical deletion |
The 24-month audit-log retention window is now enforced automatically, not just a stated target: the auditLogPrune Inngest cron runs weekly (Sundays, 04:00 UTC) and deletes InsightEvent rows older than 730 days via a SECURITY DEFINER Postgres function, the same pattern used elsewhere for RLS-protected system-wide operations.
3. Deletion triggers
Deletion is event-driven. The Shopify-mandated privacy webhooks are wired through Inngest (idempotent handlers; HTTP 202 then process) and drive the relevant purge:
customers/data_request— we assist the Controller in fulfilling a data-subject access request for that shopper. (No deletion; surfaces what is held.)customers/redact— we delete the pseudonymised customer reference and all related return/exchange records and shopper PII for that customer within the target window (§4). Because the pseudonymised reference is the join key, resolving it cascades to the dependent return data under Row-Level Security scoping.shop/redact— we purge the entire tenant: all return/exchange records, pseudonymised references, and encrypted tenant credentials for that shop are deleted/revoked. This typically fires 48 hours after a merchant uninstalls.- End of Service / contract termination — at the Controller's choice we delete or return all personal data and delete existing copies (DPA §3.8), unless retention is legally required. This does not depend on the merchant uninstalling the Shopify app (which is what triggers
shop/redactabove): a G9 staff member can trigger the identical purge directly through a superadmin-gatedtenant.offboardAndPurgeprocedure, which requires typing the tenant id to confirm, calls the same underlying tenant-purge path asshop/redact, and is itself logged as acompliance.manualoffboardpurgeaudit event. This closes the gap for a Controller who ends the Service agreement without ever removing the Shopify app.
All deletions execute under the same three-layer tenant isolation that protects the data in production — Postgres Row-Level Security (non-owner role, FORCE RLS), repository scoping, and per-agent scope — verified against live Postgres — so a deletion can never reach across tenants.
4. Target deletion window
Personal data is deleted within 30 days of a triggering event (a customers/redact or shop/redact request, or end of Service), unless a legal-hold exception applies. In practice the event-driven webhook handlers act much sooner; 30 days is the outer bound that also covers manual end-of-service offboarding.
5. Backups
Deletion from the live database does not instantly erase data already captured in managed backups. Our database (Neon Postgres, EU / eu-central, encrypted at rest including backups) uses managed backups and point-in-time recovery on the provider's standard rotation. Backups containing a deleted record age out automatically on that cycle; we do not surgically edit backup snapshots. Backed-up data remains encrypted at rest and is never restored to circumvent a deletion. Once a backup rotates out, the data is gone from it as well.
6. Legal-hold and statutory exceptions
We may retain specific data beyond the windows above only where, and for only as long as, retention is required by law or necessary to establish, exercise, or defend legal claims — for example:
- statutory bookkeeping/accounting obligations on transaction records;
- a documented legal hold relating to a dispute, investigation, or regulatory request;
- security-incident records needed for breach handling under GDPR Art. 33–34.
Where an exception applies, we retain only the minimum data necessary, isolate it from active processing, continue to protect it under the Annex II measures, and delete it once the legal basis expires. Norwegian right-of-withdrawal and consumer-protection rules are honoured and are never overridden by a shorter retention preference.
7. Sub-processors
Personal data shared with the sub-processors listed in the DPA (Annex III) — Vercel, Neon, Clerk, Inngest, Anthropic, Shopify, Posten/Bring — is subject to equivalent retention and deletion obligations flowed down by contract. On a tenant purge or end of Service, we instruct deletion at each sub-processor where the Service caused data to be stored, subject to that sub-processor's own backup rotation.
8. Changes to this policy
We may update this policy as the Service evolves. Material changes to retention windows or deletion triggers will be communicated to Controllers in line with the DPA's sub-processor/notice mechanics. This document is Version 1.0.