Deletion policy
What your application declares it keeps when a customer deletes their workspace, who answers for it, and what your integration has to do about it.
When a customer deletes their workspace, Connect purges its own copy of their data. Your application has its own copy, on your own infrastructure, and Connect cannot reach it.
The deletion policy is how you tell the customer what happens to that copy — before they confirm. It is a statement made to them, not an internal note, and it is shown to them verbatim.
An application with no policy declared is not shown as "nothing is kept". It is shown as "this application hasn't told us", which is the worst of the available answers and is what a customer sees for every application that never filled this in.
Where it fits in the deletion flow
The customer asks to delete
Connect assembles an impact report from the policy of every application the workspace subscribes to, and shows it to them. This is the moment your declaration is read.
They confirm, and the grace window opens
Access is denied immediately; the data still exists. Your application receives
workspace.deletion_scheduled carrying purgeAfter — the instant the data
stops existing on Connect's side — and policy, your own declaration frozen
as the customer read it.
The window is the longest grace period among the platform default and every subscribed application's declared one. Your policy can extend it; it can never shorten it.
They may change their mind
workspace.deletion_cancelled fires. This is the event integrations forget,
and forgetting it leaves a paying customer locked out of a product that has no
idea anything changed. Handle it.
The window closes
Connect purges. workspace.deleted fires. You do what your policy said you
would.
See Webhooks for the delivery contract behind those three events.
What you declare
Grace window
Blank accepts the platform default, which is the right answer for almost everybody. Set a number only if your own wind-down genuinely needs longer — and note that it applies to every workspace subscribed to your application, not only ones with a lot of data.
Who answers for this data
The field most designs of this shape leave out, and the one that decides what a failed deletion notice means.
| Value | Meaning |
|---|---|
connect_owned | CosmoOps runs this application. A deletion is an instruction, and a notice that never lands is our incident to chase. |
processor | A third party holds the data under contract to us. Same obligation — a deletion is still an instruction we are answerable for. |
independent | The partner decides its own retention under its own agreement with the customer. We notify; we do not instruct. |
independent is the safe default when nobody has confirmed otherwise, and it
is what a new application starts as. Choosing one of the other two is a
statement that somebody has actually taken on the obligation.
Data classes
One row per kind of data your application holds. Each carries a disposition:
- Erase — destroyed when the workspace is purged. Nothing survives.
- Anonymise — the records survive with the customer's identifiers stripped. Useful where aggregates still have to add up.
- Retain — kept intact for a set period after the workspace is gone, in a store nothing in the product can read.
retain requires a legal basis, and this is enforced rather than
encouraged: you cannot save a retained class without naming the clause or rule
that requires it. The customer is shown that string, so it should name a real
clause rather than paraphrase one — "GST records, six-year statutory
retention" rather than "compliance".
Leave the retention blank on anything that goes when the workspace does.
Held identifiers
A different clock from retention, and about the identifier rather than the data: anything allocated to the workspace that has to wait before it can be given to somebody else. A phone number. A handle. A subdomain.
Quarantining a phone number for 90 days is not retaining the customer's data — it is refusing to hand their old number to a stranger the day after they leave.
Export before purge
A promise that your application will let the customer take their data before the window closes. Leave it off until that export actually exists; it appears in the impact report as a commitment.
Versioning
version is stamped server-side on every save. Every deletion certificate
written afterwards records the version that was in force, so an operator saving
twice is visible in the record, and a policy that changed after a customer
confirmed cannot be presented as the one they read.
What your integration owes
The policy is a declaration; these three handlers are the implementation of it.
export const POST = createConnectWebhookHandler({
secret: process.env.CONNECT_WEBHOOK_SECRET!,
on: {
// Access is already denied on Connect's side. Start your own wind-down on
// the clock `purgeAfter` gives you.
"workspace.deletion_scheduled": ({ workspaceId, purgeAfter }) =>
scheduleWindDown(workspaceId, new Date(purgeAfter)),
// The one that gets skipped. Without it a customer who changes their mind
// stays locked out of your product forever.
"workspace.deletion_cancelled": ({ workspaceId }) => cancelWindDown(workspaceId),
// Drive this off `policy`, not off a second copy of it in your code.
"workspace.deleted": async ({ workspaceId, policy }) => {
for (const dataClass of policy?.dataClasses ?? []) {
if (dataClass.disposition === "erase") await erase(workspaceId, dataClass.key);
if (dataClass.disposition === "anonymize") await anonymize(workspaceId, dataClass.key);
if (dataClass.disposition === "retain") {
// The number you declared, as Connect promised it to the customer.
await archive(workspaceId, dataClass.key, dataClass.retainDays);
}
}
for (const release of policy?.resourceReleases ?? []) {
await holdBeforeReissue(workspaceId, release.key, release.quarantineDays);
}
},
},
});Both deletion events carry policy — the same frozen snapshot, including
version, every class's retainDays and every held identifier's
quarantineDays. Read it rather than hardcoding the figures you typed into
Console: the event is the version the customer was actually shown, and your
copy drifts the moment somebody edits the policy. It is null only for a
deletion confirmed before policies existed, and an application that declared
nothing gets empty arrays rather than a missing key.
Webhooks are the fast channel and are at-least-once. The readiness endpoint remains authoritative — reconcile against it rather than trusting arrival order, and make each handler idempotent, because you will see the same event twice.
Authorization — permissions and roles
What your application declares, what Connect resolves, and why Connect never enforces your permissions for you.
Compliance — the notice you publish
Why the grievance officer your privacy page names is Connect's and not yours, and how to read it instead of copying it.