SaaS Account Deletion and Data Export | Coding Capybaras

How to build saas account deletion and data export the way regulators expect: what must be erased, what you are allowed to keep, deadlines, and the flow.

· Justin Boggs

A pile of finely shredded white paper on a tabletop

Photo by Mahen Rin on Unsplash

SaaS account deletion and data export are the two features regulators actually check, and both are narrower than founders fear. You need a way for a user to request erasure of their personal data and get a machine-readable copy of it, you need to act within one month under GDPR or 45 days under the CCPA, and you need to keep the records you're legally required to keep — invoices, tax records, anything tied to a live legal claim. That last part surprises people: "delete everything" is not the standard, and building it that way will break your accounting. Here's what the rules actually say and how to build both features in a weekend.

TL;DR

  • Under GDPR you have one month to respond to an erasure request; under CCPA you confirm receipt in 10 business days and respond substantively within 45 calendar days.
  • The right to erasure is not absolute. You may keep data needed to comply with a legal obligation or to defend legal claims — which covers invoices and tax records.
  • Stripe won't let you redact issued invoices at all, for tax-integrity reasons. Design around that from day one.
  • Data export needs to be structured, commonly used, and machine-readable. JSON or CSV is fine; a PDF is not.
  • Build it self-serve. A deletion flow that requires emailing support is a deadline you will eventually miss.

I'm a founder, not a lawyer, and this is an engineering post rather than legal advice. If you're handling health data, financial data, or operating at real scale, get an actual privacy lawyer to review your retention schedule.

What do the regulations actually require?

Two rights are in play: the right to have your personal data erased, and the right to receive a copy of it in a portable format. GDPR calls them the right to erasure (Article 17) and the right to data portability (Article 20). The CCPA, as amended by the CPRA, calls them the right to delete and the right to know.

Under GDPR, a person can require erasure on any of six grounds — among them that the data is no longer necessary for the purpose you collected it for, that they withdraw the consent you were relying on, that they object and you have no overriding legitimate interest, or that you processed it unlawfully. For a typical SaaS, "I'm closing my account" triggers the first of those and that's the one you'll actually handle.

The UK ICO's guidance fills in the operational details that matter more than the statute text:

  • A request can be verbal or written, made to any part of your organisation, and doesn't have to say "Article 17" or "erasure." A support email saying "please wipe my account" is a valid request.
  • The clock is one month from the day you receive it, calendar date to calendar date. Receive it on 3 September, respond by 3 October.
  • You can extend by two further months if the request is genuinely complex, but you must tell the person why within the first month.
  • You can't charge a fee except for requests that are manifestly unfounded or excessive.
  • If you need ID verification, the clock starts when you receive the ID, not the original request — but you must ask within one month.

CCPA runs on different numbers. Per the California Privacy Protection Agency, a business must confirm receipt of a delete, correct, or know request within 10 business days and respond substantively within 45 calendar days, extendable by another 45 for a total of 90. Opt-out-of-sale requests are much tighter: 15 business days, maximum.

| | GDPR / UK GDPR | CCPA (as amended by CPRA) | | --- | --- | --- | | Deadline to respond | 1 month from receipt | 45 calendar days | | Extension available | +2 months, if complex | +45 days, if you notify | | Acknowledge receipt | Not separately required | Within 10 business days | | Valid request format | Verbal or written, any channel | Via designated methods in your privacy policy | | Who it covers | People in the EU/UK | California residents | | Applies to a small SaaS? | Yes, if you have EU/UK users | Only above revenue or volume thresholds |

That last row is worth dwelling on. The CCPA only applies to for-profit businesses that do business in California and clear one of three thresholds: gross annual revenue of $26.625 million or more (the 2025 inflation-adjusted figure), buying, selling, or sharing the personal information of 100,000 or more California residents or households, or deriving 50% or more of annual revenue from selling or sharing personal information. Most indie SaaS companies clear none of these. GDPR has no such threshold — one paying customer in Berlin and you're in scope.

Which means: build to the GDPR standard, because it's stricter and it applies to you sooner. You'll be CCPA-compliant along the way. The GDPR basics for indie SaaS post covers the broader obligations; this one is just the two deletion-and-export features.

What are you allowed to keep?

This is the section founders skip and then regret. The right to erasure is not absolute, and building a delete button that truly nukes everything will break your books.

GDPR Article 17(3) lists the exceptions. The two that matter for SaaS are processing necessary "for compliance with a legal obligation" and processing necessary "for the establishment, exercise or defence of legal claims." Tax law requires you to keep transaction records for years. A chargeback dispute or a threatened lawsuit means you need the account history. In both cases you can refuse the erasure request for that specific data — you just have to tell the person you're doing it and why.

Stripe makes this concrete in a way that's hard to argue with. Their deletion-requests documentation states that the Redaction API "doesn't support invoice redactions, because issued invoices can be subject to tax-integrity and record-retention requirements." You cannot delete an issued invoice from Stripe. Finalized invoices and subscription invoices must be voided, not deleted; only unfinalized one-off invoices can be removed. Stripe also notes that transactions can only be redacted after 90 days, and that you must finalize object state first — cancel unused PaymentIntents, detach payment methods, close open disputes.

So your delete flow has to be selective from the start. Roughly:

| Data | What happens on deletion | Why | | --- | --- | --- | | Name, email, avatar, profile fields | Erase | Core personal data, no retention basis | | User-generated content | Erase or anonymize (your call, stated up front) | Depends on whether others depend on it | | Session tokens, API keys | Erase immediately | Also a security requirement | | Analytics events | Strip or hash identifiers | Irreversibly de-identified data leaves scope | | Invoices and payment records | Retain, detached from the profile | Tax obligation; Stripe won't redact them anyway | | Audit and security logs | Retain, minimized | Legal claims and fraud defence | | Backups | Put "beyond use", let them age out | See below |

Backups deserve their own note because they cause the most anxiety. The ICO's position is pragmatic: you do have to address backups, but "it may be that the erasure request can be instantly fulfilled in respect of live systems, but that the data will remain within the backup environment for a certain period of time until it is overwritten." The key phrase is "beyond use" — you don't use the backup data for any other purpose, the backup simply sits there until the retention schedule replaces it. What you must not do is restore a backup and silently resurrect a deleted user. Your restore runbook needs a step that re-applies any deletions processed since the snapshot. If you don't have a restore runbook at all, the security incident plan post is the place to start.

And whatever you decide, say it out loud. The ICO is explicit: "You must be absolutely clear with individuals as to what will happen to their data when their erasure request is fulfilled, including in respect of backup systems." That sentence belongs in your privacy policy, in plain English, next to the retention periods. If you're writing that policy, the terms of service and privacy policy guide covers the structure.

How should the deletion flow actually work?

The pattern that satisfies both the regulations and your support inbox is a three-stage flow: request, grace period, execution.

flowchart TD
    A["User clicks Delete account"] --> B["Re-authenticate<br/>password or magic link"]
    B --> C["Type account email to confirm"]
    C --> D["Mark deletion_requested_at<br/>send confirmation email"]
    D --> E{"28-day grace period"}
    E -->|"User signs in and cancels"| F["Clear the flag<br/>account restored"]
    E -->|"Period elapses"| G["Cancel Stripe subscription<br/>void open invoices"]
    G --> H["Erase profile + content<br/>anonymize retained rows"]
    H --> I["Write tombstone audit record"]
    I --> J["Send deletion-complete email"]

A few decisions inside that flow are worth defending.

Re-authenticate before accepting the request. Account deletion is the single most destructive action in your product. An unattended laptop shouldn't be enough. This is also your ID verification step for regulatory purposes — the person is already authenticated as the account holder, which is far stronger evidence than an email exchange.

Use a grace period, not an instant wipe. A waiting period catches the two failure modes that generate the angriest support tickets: the user who changes their mind, and the compromised account where an attacker tries to destroy someone's data. Say the length in the confirmation email.

Pick the length carefully, though. The GDPR clock starts on receipt of the request — you don't get to restart it at execution — and one month means calendar date to calendar date, so a request received on 1 February is due on 1 March. That's 28 days, not 30. The ICO's own suggestion is to "adopt a 28-day period to ensure compliance is always within a calendar month," which is why I'd use 28 rather than the more common 30. What actually satisfies the deadline is the confirmation you send on day one, telling the person their data will be erased and when; the grace period is an operational courtesy layered inside it.

Cancel billing first. Deleting the user row while an active Stripe subscription keeps charging a card is how you turn a compliance feature into a chargeback. Cancel the subscription, then handle invoices per Stripe's rules — void what's finalized, delete what's still a draft. The cancel and offboarding flow post covers the billing side in more depth.

Anonymize rather than delete where records must survive. If an orders row has to persist for tax purposes, null out the personal fields and replace the foreign key with a permanent deleted-user sentinel. Anonymization is not the same as pseudonymization: replacing an email with a hash you could reverse or re-link doesn't take the record out of scope. The severing has to be irreversible.

Write a tombstone. Keep a minimal record — a hash of the email, the request timestamp, the completion timestamp, the categories erased. That's how you prove compliance later, and the ICO expects you to be able to demonstrate it. Keep it small enough that the tombstone itself isn't a new privacy problem.

In a Next.js app with Postgres, the schema side is small: a nullable deletion_requested_at timestamp on the user row, a scheduled job that sweeps for expired requests, and a transactional function that does the erasure. The sweep is a good fit for a daily cron job. Validate the request with Zod in the server action before anything else happens, and log every stage.

What goes in the data export?

Article 20 sets a specific bar: the data must be provided in a "structured, commonly used and machine-readable format." The regulation names no formats, but JSON and CSV clearly clear the bar, so that's what I'd ship. I wouldn't send a PDF of a rendered account page — it's hard to argue it's machine-readable in the sense intended, and it fails the underlying purpose of the right, which is letting someone move their data to a competitor without retyping it.

What to include: everything the user provided, plus everything you observed about them. Profile fields. Every record they created. Their billing history at the summary level — dates, amounts, plan names, invoice IDs — even though the invoices themselves live at Stripe. Settings and preferences. Team memberships. If you track usage events tied to their account, those too.

What to exclude: your inferences and derived data, if they're genuinely your own analysis rather than their data. Other users' personal data — a shared workspace export should not hand someone their colleague's email address. Security material like password hashes and API secrets.

Shape it for a human as well as a machine. A ZIP containing one account.json with profile and settings, plus a CSV per collection, is easy to produce and easy for a recipient to actually use. The CSV import and export post covers the generation mechanics — the same code path serves both features, which is a nice bonus: the export you build for the "download my data" button is often the same export your customers want for normal operational reasons.

Two operational notes. Generate exports asynchronously and email a signed, expiring download link rather than blocking a request while you serialize a large account. And rate-limit the endpoint: CCPA lets a business decline a request to know if it has already provided the information more than twice in a 12-month period, which is a sensible ceiling to enforce in code rather than in a support conversation.

Offer the export before the delete confirmation, too. A "download your data first?" step in the deletion flow costs you nothing, reduces regret-driven support tickets, and demonstrates good faith if a regulator ever asks how you handle these requests.

How do you ship this without a support ticket?

The strongest argument for self-serve isn't user experience. It's that a manual process has a deadline attached to it and you will eventually be on vacation when the clock starts.

Think about the failure case honestly. A user emails "delete my account" on a Friday. It lands in a shared inbox. You're launching something, it gets buried, and three weeks later you find it. You're now days away from a GDPR breach on a request you fully intended to honour. Nothing about that requires bad intent — just a normal week.

A self-serve flow takes the deadline off your calendar, because the response goes out the moment the request is made and the erasure runs on a timer rather than on your attention. Four things make it work:

Put it where people look. Account settings, bottom of the page, plainly labelled "Delete account" and "Download your data." Not buried in a help centre article. Not a mailto link. The CCPA is explicit that businesses must designate methods for submitting requests and disclose them in the privacy policy; for an online-only business, an email address is the minimum, but a button in the product is better.

Handle the requests that still arrive by email. Some always will, and under GDPR a verbal or written request to any channel counts. Train whoever reads support email to recognise them, and give them an admin action that kicks off the same flow the button does — so there's one code path and one audit trail.

Make the copy honest. Tell people what survives deletion and why, right there in the confirmation dialog. "Your profile, content, and settings will be permanently erased. Billing records are retained for tax purposes." Two sentences. Users accept this readily; what they don't accept is discovering it later.

Test it against a real account. Create a throwaway account, give it a subscription, some content, and a support conversation, and run it through the flow end to end. Then check every table and every third-party service — Stripe, your email provider, your analytics, your support tool — and confirm the data is actually gone from each. Every processor you send data to is part of your deletion obligation, and the ICO requires you to inform recipients you've shared data with unless it's impossible or takes disproportionate effort. That test is also the thing an AI coding assistant is least likely to get right on its own; a plan that looks complete in the diff can still miss a table. Reviewing that kind of change is the whole subject of the AI code review post.

Frequently asked questions

How long do I have to respond to a deletion request?

Under GDPR, one month from the day you receive the request, calculated to the corresponding calendar date. You can extend by two further months for genuinely complex requests if you notify the person within the first month. Under CCPA, confirm receipt within 10 business days and respond substantively within 45 calendar days, extendable by another 45.

Do I have to delete data from my backups?

You have to address it, but you don't have to restore and rewrite every snapshot. The ICO's guidance is that you delete from live systems immediately and put the backup data "beyond use" — don't use it for anything, let it age out on your normal rotation. The critical requirement is that restoring a backup must not silently bring a deleted person back into production.

Can I refuse a deletion request?

Sometimes. GDPR Article 17(3) lets you keep data where processing is necessary to comply with a legal obligation or to establish, exercise, or defend legal claims — which covers tax records and disputed transactions. You can also refuse requests that are manifestly unfounded or excessive. In every case you must tell the person within the deadline that you're refusing, and the ICO says you should explain your reasons and their right to complain to a supervisory authority.

What format does a data export need to be in?

Structured, commonly used, and machine-readable. The regulation doesn't name formats, but JSON and CSV both clearly qualify. A PDF or a rendered HTML page is a poor fit, because the point of the right is portability — someone should be able to load the file into another service without retyping it.

Does the CCPA apply to my small SaaS?

Probably not yet. It applies to for-profit businesses doing business in California that have $26.625 million or more in gross annual revenue, handle the personal information of 100,000 or more California residents or households, or derive 50% or more of revenue from selling or sharing personal information. GDPR, by contrast, has no threshold — a single EU customer puts you in scope.

What about the invoices in Stripe?

You can't delete them. Stripe's documentation states the Redaction API doesn't support invoice redactions because issued invoices are subject to tax-integrity and record-retention requirements. Finalized and subscription invoices get voided; only unfinalized one-off invoices can be deleted. Plan your deletion flow around retaining billing records rather than erasing them.

Wrapping up

The reason I'd build saas account deletion and data export early, before you have a single European customer, is that both get harder as the schema grows. Adding a deletion path to four tables is an afternoon. Adding it to forty, across three third-party services, with a billing history you can't legally touch, is a project — and it'll land on your desk with a one-month deadline already running.

Build it selective, not total. Erase the personal data, anonymize what must survive, retain what the law requires you to retain, and write down which is which. Make both features self-serve so no deadline ever depends on you checking an inbox. And test the whole thing against a real account with a real subscription, because the gap between "the code deletes the user row" and "the person is actually gone from every system" is where the real risk lives.

If you're building this into a Next.js, Supabase, and Stripe app, Coding Capybaras is the free boilerplate I built for that stack — the schema conventions, server-action validation, and Stripe webhook handling above are already wired in.