DocsCore concepts

Core concepts

Data lifecycle and privacy

5 min readReviewed July 2026

Analytics data often looks harmless until several fields are combined. A stable identifier, page path, timestamp, and account name can reveal a great deal about a person. Treat an event pipeline as a production data system: decide what belongs in it, limit how long it remains useful, and document how you respond when data must be corrected or removed.

This guide describes practical engineering controls. It is not legal advice; your obligations depend on where you operate, what you collect, and the agreements you have with customers.

Start with purpose, not properties#

Every event should answer a product or operational question. Write that question next to the event in your tracking plan, then collect only the fields needed to answer it.

Use three tests for every proposed property:

  1. Decision: what decision will this property change?
  2. Sensitivity: would exposure harm a user or customer?
  3. Lifetime: how long will the value remain useful?

If the first answer is unclear, do not collect the field. If the second answer is yes, look for a less sensitive substitute. If the third answer is short, put the data in a collection with a matching retention period.

Classify fields before shipping#

A lightweight classification is usually enough:

Class Examples Recommended treatment
Product metadata feature name, plan tier, app version Usually appropriate for analytics
Pseudonymous identifiers internal user ID, account ID Keep stable, opaque, and access-controlled
Personal data email, name, IP address Avoid unless the use case truly requires it
Sensitive data credentials, tokens, health or payment details Do not send to GraphJSON

An opaque identifier is still linkable data. Replacing an email address with usr_8f2a reduces exposure, but it does not make the record anonymous when your application can map that value back to a person.

Never log passwords, session cookies, API keys, authorization headers, payment card data, private message bodies, or unfiltered request and response objects. Redact at the source, before data leaves your application.

Prefer stable, opaque IDs#

Use application-generated IDs such as user_id and account_id. Avoid using an email address as an identifier: emails change, they are directly identifying, and they are easy to expose accidentally in chart labels or exports.

{
  "event": "report_exported",
  "user_id": "usr_8f2a",
  "account_id": "acct_210",
  "format": "csv"
}

Keep the mapping from usr_8f2a to a real person in your system of record. This makes access control, correction, and deletion workflows easier to reason about. Read Users, accounts, and identity for the full identity model.

Put boundaries around collections#

Collections are useful privacy and lifecycle boundaries. Separate data when it needs a different audience, retention period, or operational purpose.

For example:

  • product_events for long-lived, pseudonymous product usage
  • delivery_diagnostics for short-lived webhook and error details
  • public_status_metrics for aggregate, non-user-level measurements

Set an optional retention period on collections that do not need permanent history. A short diagnostic stream should not inherit the lifetime of a strategic product dataset.

Deleting a collection permanently deletes its events. Confirm that downstream dashboards, alerts, queries, and embeds no longer depend on it before deleting it.

When collection depends on consent or another application-level policy, enforce that rule where the event is created. Do not send the event and plan to filter it out later.

if (analyticsAllowed(user) && !user.isInternal) {
  await analytics.track({
    event: "feature_used",
    user_id: user.id,
    feature: "bulk_export",
  });
}

Centralize this decision in one server-side tracking function. That gives you a single place to:

  • suppress events for users who opt out
  • exclude employees and test accounts
  • remove forbidden properties
  • route development data away from production
  • attach the current schema_version

Client-side consent state can inform the decision, but sensitive credentials and authoritative policy checks belong on your server.

Build a data-subject request runbook#

GraphJSON stores the JSON you send; it does not automatically maintain a user profile or identity graph. Your system should retain the mapping between an internal person and every identifier used in analytics.

A practical request runbook is:

  1. Verify the requester and determine the applicable scope.
  2. Resolve the person to all historical identifiers, including anonymous IDs that were intentionally linked.
  3. Search the relevant collections and time range to understand the affected data.
  4. Stop new collection when required.
  5. Contact hi@graphjson.com for a scoped removal or correction request. Include workspace and collection names, identifier fields, identifier values, and the required time range. Do not send API keys.
  6. Record what was removed from GraphJSON and every upstream or downstream system.
  7. Verify that retries, replay queues, and historical import jobs cannot reintroduce the records.

Do not promise selective deletion until you have tested this procedure with your actual schema. If you need deletion to be self-service or subject to a specific service-level agreement, discuss the requirement with GraphJSON before choosing the system for that dataset.

Protect shared and embedded data#

Dashboards and exports can reveal sensitive data even when the underlying event is appropriate. Use aggregate charts where possible, avoid dimensions that display personal values, and review CSV exports as a separate access path.

An embed URL is a bearer capability: anyone who obtains it may be able to view the graph. For customer-facing analytics, generate personalized embed URLs on your server, constrain them to the authenticated account, and never place a reusable cross-customer URL in client code.

Prepare for mistakes#

If sensitive data is accidentally ingested:

  1. disable or patch the emitting code
  2. pause any replay worker that contains the bad payload
  3. identify the collection, fields, identifiers, and time window
  4. restrict affected dashboards, embeds, and exports
  5. contact hi@graphjson.com with the removal scope
  6. follow your organization’s incident-response and notification process

Do not paste the sensitive value into support tickets or chat. Refer to field names and opaque identifiers whenever possible.

Production checklist#

  • Every event has a documented product or operational purpose.
  • Direct identifiers are excluded unless explicitly approved.
  • Secrets, credentials, and raw sensitive payloads are blocked at the source.
  • Collection boundaries match access and retention needs.
  • Consent and employee/test filtering happen before ingestion.
  • User and account IDs can be resolved for access or deletion requests.
  • Dashboard, export, alert, and embed access has been reviewed.
  • A deletion and accidental-ingestion drill has been completed.

Pair this review with capacity, retention, and cost planning and export, deletion, and portability so the useful lifetime of a dataset is reflected in policy, configuration, and operational procedures.

Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support