On this page

Privacy controls

Every control on this page is one that exists in the SDK or the backend today. Where a platform is weaker than the others, it says so on that row rather than in a footnote.

Pass the argument below and the SDK installs paused: hooks are attached, nothing is captured and nothing is queued. Call the opt-in method once the user agrees.

The consent gate argument on each platform
PlatformStart pausedResume
FlutterDrengr.start(enabled: false)Drengr.optIn()
iOSDrengr.start(startEnabled: false)Drengr.optIn()
AndroidDrengr.start(startEnabled = false)Drengr.optIn()
Webstart({ enabled: false })optIn()

Opt-out and opt-in

Opt-out is persistent and outranks the start argument: an opted-out install stays paused on every later launch until you opt back in. Where that persistence is written differs by platform, and one of them is weaker than the rest.

Opt-out API and persistence per platform
PlatformOpt outWhere the choice is stored
FlutterDrengr.optOut()SharedPreferences (drengr.opt_out), mirrored to a temp marker for the synchronous check at start
iOSDrengr.optOut()UserDefaults, key dev.drengr.opt_out
AndroidDrengr.optOut()SharedPreferences file drengr_sdk, key opt_out
WeboptOut()localStorage, key drengr_opt_out

Every SDK records the decision in the platform's durable store: localStorage on web, SharedPreferences on Android and Flutter, UserDefaults on iOS. An opted-out install stays opted out across restarts and app updates.

You can still hold your own consent record and pass it in, which is worth doing if consent is something you must be able to produce on request:

lib/main.dartdart
// Your own consent record is the source of truth. Read it first,// then let the SDK's own opt-out act as a second gate.final consented = await myConsentStore.hasConsented();Drengr.start(  publishableKey: 'drengr_pk_YOUR_KEY',  appPackage: 'com.example.myapp',  enabled: consented,);

Do Not Track and GPC

We would rather say this plainly than let an unchecked box imply a control we have not built.

ts
// Web: honour GPC / DNT yourself until the SDK does it natively.
const nav = navigator as Navigator & { globalPrivacyControl?: boolean };
const refused = nav.globalPrivacyControl === true || nav.doNotTrack === '1';

start({ publishableKey: 'drengr_pk_YOUR_KEY', enabled: !refused });

How long data is kept

Events are kept for 180 days and then dropped by the storage engine itself. There is no job to fail and no switch to forget to flip.

Retention windows by table
WhatWindowCounted from
Raw events180 daysingested_at
Derived semantic events180 daysingested_at
Request-shape inventory180 dayslast_seen

Why the clock starts at receipt, not at the event

The window keys off ingested_at, the server clock when we received the row, rather than occurred_at, the client clock when it happened. Two reasons, and the second is the one that bites.

A device that has been offline for a week, or one whose clock is simply wrong, would otherwise deliver rows that are already older than the window. Keying off receipt means a backfilled row is never born expired: it gets the full 180 days from the moment it reaches us. It also lets whole partitions drop cleanly instead of deleting scattered rows.

Deleting a person's data

One authenticated call erases a subject from both the raw and derived stores. The tenant is taken from your credentials, never from the request body, so you cannot reach another organisation's rows.

bash
curl -X POST https://analytics.drengr.dev/functions/v1/forget-events \
  -H "Authorization: Bearer $DRENGR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"install_id": "6f1c…"}'
Shortened for reading, so it is not offered as copyable.

The body takes exactly one selector. Sending both, or neither, is rejected.

Selectors accepted by the deletion endpoint
SelectorUse it when
install_idYou are erasing one app install. This is the identifier the SDK generates and the only one that exists for a user who never signed in.
subject_hashYou are erasing a signed-in person across every device they used. This is the pseudonymous join key derived from the id you passed to identify().

Deriving subject_hash from your own user id

You never send us a raw email or user id to delete. subject_hash is sh_followed by base64url of HMAC-SHA256 over your organisation's pepper and the normalised id. The SDK computes it on the device, and the same input always produces the same key, so you can recompute it server-side to build a deletion request.

What a GDPR or CCPA request maps to

Data subject requests mapped to concrete Drengr operations
RequestWhat you do
Erasure, or “delete my data”POST to forget-eventswith the person's subject_hash, or their install_id if they never signed in.
Objection, or “stop collecting”Call the platform's opt-out. On Flutter, also record the choice in your own storage, per the caution above.
Withdrawing consentOpt out to stop future capture, then call the deletion endpoint to remove what was already collected. Opt-out on its own is not retroactive.
Access, or “what do you have on me”Query the console scoped to that subject_hash. The columns we store are listed on every platform page under “What arrives on its own”.
Sealed personal dataDetected values are encrypted on the device to your organisation's key. We hold ciphertext we cannot open, so erasure removes the envelope without us ever having read it.

Drengr Actuator, the device-driving MCP server, is a separate product with its own erasure path. See drengr uninstall.

This page describes mechanisms, not legal advice. Our own commitments are in Privacy and Security.