Data retention on revoke
When a user disconnects your app at Whisp3r Auth, what happens to their data on your side is your decision — but you tell them in advance what to expect and you respect their intent at revoke time. Three independent commitments, one privacy-policy link, and a webhook payload that carries the user's choice through to your receiver.
The model in one paragraph
Apps declare which of three commitments they're willing to honour: delete, archive, regulatory retention. They're independent — an
app can commit to deletion AND archive (offering users a choice),
or to regulatory retention only (banks, healthcare). At consent
time the user sees all declared commitments along with a link to
your privacy policy. At revoke time the user picks one of the
user-actionable options (delete / archive); the regulatory
retention disclosure is always-on and can't be opted out of —
users can read your privacy policy to understand what's kept. The
webhook delivers the resolved data_intent to your
app.
Declaring your commitments
Each app's OAuth metadata holds a retention_commitments object with three booleans plus a privacy_policy_url pointing at your privacy policy.
{
"privacy_policy_url": "https://your-app.example/privacy",
"retention_commitments": {
"accepts_delete": true,
"accepts_archive": true,
"retains_for_regulatory": false
}
} accepts_delete- If the user picks "Delete my data" on the revoke screen, your app will delete it. Privacy-first apps should declare this.
accepts_archive- If the user picks "Archive my data" on the revoke screen, your app hides the user's content but preserves it so the user can restore it by re-consenting. Useful for social / creator apps where users may want to take a break without losing their history.
retains_for_regulatory- Your app keeps linked records that the user cannot delete because of legal/regulatory obligations (banking, healthcare, tax-regulated services). Always-on disclosure — surfaced on both the consent screen and the revoke screen.
At least one commitment must be declared — an app with no commitments offers the user no agency. Privacy-policy URL is required and must be HTTPS.
What the user sees
Consent screen
When granting access to your app, the user sees:
- "This app will delete your data if you revoke access" — if
you declared
accepts_delete. - "This app can archive your data if you revoke access. You can
restore by re-consenting." — if you declared
accepts_archive. - "This app retains certain data for regulatory purposes that
cannot be deleted." — if you declared
retains_for_regulatory. - A link to your privacy policy.
Revoke screen
When disconnecting your app, the user sees:
- Always-on: the regulatory disclosure with the
privacy policy link — if your app declared
retains_for_regulatory. - Choices: "Delete my data" if your app
declared
accepts_delete; "Archive my data (restore on re-consent)" if your app declaredaccepts_archive; "Keep my data" always available as a no-op preserve option.
If the user picks an option you didn't declare a commitment for, auth falls back to your strongest commitment (delete > archive > retain) — never silently honours a choice you didn't make.
The mental model: sub stays stable
Revocation severs the connection, not the
identifier. The pairwise sub with your app is
deterministic
(HMAC(user_id, client_id, server_secret)) and does
not rotate on revoke. Re-consent yields the same sub.
This is the right default because accidental revoke shouldn't be
catastrophic and users routinely disconnect / reconnect. Real
privacy comes from data deletion, not identifier
rotation: if you declared accepts_delete and
actually deleted, a returning sub sees a user with no data —
indistinguishable from a brand-new account from your code's
perspective.
Webhook payload
POST <your webhook_url>
X-Whisp3r-Signature: t=<unix>,v1=<hex>
X-Whisp3r-Event: user.consent.revoked
{
"event": "user.consent.revoked",
"sub": "<pairwise sub for your client_id>",
"client_id": "<your client_id>",
"data_intent": "delete" | "archive" | "retain",
"commitments": {
"accepts_delete": true,
"accepts_archive": true,
"retains_for_regulatory": false
},
"delivered_at": "2026-06-04T..."
} data_intent- The resolved intent — what the user wants done with their
data. One of
delete,archive,retain. Already filtered against your declared commitments. commitments- Your app's currently-declared commitments at the moment of revoke. Lets your receiver verify the intent is consistent with what you've publicly promised.
Compliance + legal context
Auth can't compel you to act on the intent. Apps with legal
retention obligations may decline to delete even when the user
asks — that's why retains_for_regulatory exists as a
separate, always-on disclosure. The user is informed; your app
is the source of truth for what's actually kept. Disclose this
clearly in your privacy policy.
Self-hosted mode (forward-compatible)
When the directory adds support for users hosting their own
content, an additional commitment shape will be added for apps
with regulatory retention obligations: "may retain copies even
if the user self-hosts." That distinction is moot today (every
user is corporate-hosted on the connected app's infrastructure)
but the metadata shape will be additive — apps declaring retains_for_regulatory today won't need migration.