Privacy & PII

Onelo Monitor scrubs sensitive data at three layers — SDK preflight (every SDK), the backend ingest endpoint, and a Postgres trigger that runs on every insert. This page documents what gets stripped, what stays, and what you control.

Three-layer scrubbing

Defense in depth: even if the SDK preflight is bypassed (curl, a malicious client, an old SDK version), the backend ingest endpoint re-runs the full key + value scrub on every request, and the database trigger redacts the key denylist again before the row is committed.

LayerWhereCoverage
SDK preflightEvery SDK — Swift, JS, Electron, React Native, Android, Flutter, Python, Node.js, PHP (each ships its own scrubber module)Key denylist + value-regex on meta and error, before anything leaves the device/process.
Backend ingestbackend/app/routes/sdk_monitor.py — on every events/batch requestKey denylist + value-regex on meta and error. Applies to ALL platforms and raw HTTP clients.
Postgres triggermonitor_events_pii_scrub — BEFORE INSERT/UPDATE on monitor_eventsKey denylist on meta. Last line of defense.
The value-regex (Bearer / JWT / Stripe / credit-card patterns) runs in the SDK preflight and on the backend — so it protects every platform even if an SDK is outdated. The Postgres trigger is key-denylist-only to keep DB cost low.

What gets scrubbed (key denylist)

When any key inside meta matches the regex below (substring, case-insensitive) — or contains a standalone key segment like x-anthropic-key — the value is replaced with [REDACTED]. Backend scrubbing recurses through nested objects up to 10 levels deep.

regex
password|passwd|secret|token|api_key|apikey|authorization|cookie|credential|private_key|client_secret|cvv|ssn

Whenever a key is redacted, the scrubber (SDK preflight and backend alike) adds an audit list at meta._onelo_redacted so you can see what was stripped without exposing the raw values.

json
// before
{ "user": { "id": "u_42", "api_key": "onelo_sk_live_…" } }

// after
{
  "user": { "id": "u_42", "api_key": "[REDACTED]" },
  "_onelo_redacted": ["api_key"]
}

What gets scrubbed (value regex)

Even if the surrounding key is innocuous (notes, message, error), values matching these patterns are still redacted in-place. Boundary anchors are ASCII-only so non-ASCII padding cannot escape.

PatternExample
Bearer tokensBearer eyJ0eXAi…
JWTs (3-part dotted base64url)eyJhbGciOi… . eyJzdWIi… . sig
Stripe-style keyssk_live_…, sk_test_…, pk_live_…, pk_test_…, rk_live_…, rk_test_…
Onelo keysonelo_pk_test_…, onelo_pk_live_…, onelo_sk_…
Credit cards (separated)4242 4242 4242 4242
Credit cards (unseparated)4242424242424242
Amex format3782 822463 10005

Source: backend/app/routes/sdk_monitor.py (PII_VALUE_PATTERNS).

What does NOT get scrubbed by default

A few categories are intentionally left alone — useful for debugging, low PII risk, or opaque enough that they can't be reversed.

FieldWhyOpt out
Email addressesOften the only useful debugging signal.Python SDK: pass strict_email_scrub=True. Other SDKs: filter manually before passing to meta.
userIdOpaque string ≤ 256 chars. Not hashed by Onelo.Pass an opaque internal id instead of a real identifier (e.g. SHA-256 of email).
Free-form error string (≤ 8 KB)Stack traces are essential for triage.Value-regex still runs inside it — Bearer tokens, JWTs, etc. are stripped.

Retention

Retention is plan-gated. The dashboard query window equals the storage retention on every plan.

PlanStorage retentionDashboard window
Free7 days7 days
Pro30 days30 days
Business90 days90 days

Cleanup is a nightly pg_cron job at 03:00 UTC that purges monitor_events, monitor_runs (the full per-run history), monitor_aggregates, and monitor_checks per plan. Resolved incidents are purged at 2× retention so you keep historical context for postmortems.

Data location

Monitor events are stored in Onelo's Supabase deployment, hosted in the EU. Specific region details are available on request — email [email protected].

GDPR compliance

TopicStatus
RoleOnelo is a data processor for monitor events. You (the developer) are the controller.
DSR — exportExport runs via the API (GET /api/monitor/features/{name}/runs/export?format=csv|json) and filter the file by userId offline — a userId filter parameter does not exist yet.
DSR — deleteNo per-user delete endpoint yet. Workarounds: delete the app (CASCADE), or contact support for manual deletion. On the roadmap.
DPAAvailable on request — mailto:[email protected].
Sub-processorsSupabase (Postgres, hosted on AWS) and a transactional email provider (SMTP). Full current list available on request.
Honest gap. A self-serve DSR delete endpoint (DELETE /api/monitor/users/{userId}) does not exist yet. If you need to honor a delete request before that ships, email support with the app id and the userId — we'll execute it manually.

Anonymous mode

Onelo Monitor does not auto-collect user identifiers. To track anonymously, simply don't call setUserId — events land with userId = null. To track with identifiers, call setUserId(opaque_id); the server stores it as an opaque string with no further processing.

Consent integration

If you collect runs after a user has revoked tracking consent, that is your responsibility. The Onelo SDK does not gate track() / event() on consent. Wrap calls in your own check, or call destroy() to stop the SDK entirely.

A dedicated /docs/consent page is on the roadmap. Until then, see docs/consent-system.md in the repo for the waitlist-flavoured implementation — the same patterns apply.
Monitor — Privacy & PII | Onelo Docs