Guides
Privacy & consent
Consent isn't a setting Pharen bolts on — it's stamped on every event and enforced at the point the SDK collects anything. This page is the honest account of what that means in practice.
Consent on every event
Every event Pharen sends carries an explicit consent context — the set of purposes granted, and when. It's present even when nothing is granted (an empty set is still explicit). An event is only queued if the purpose it needs is currently granted; otherwise it's dropped at the boundary, before it's ever stored or sent.
The five purposes
Purposes map to why data is processed, not to product features. Each event type resolves to exactly one:
| Purpose | Covers |
|---|---|
telemetry | Crashes, handled errors, performance. |
behavioral | Sessions, screen views, your track() events. |
identity | Associating activity with a known user (identify()). |
content | User-submitted content, such as feedback. |
health | Special-category / sensitive data. Explicit, runtime-only (see below). |
Opt-in by default
Nothing is granted until you grant it. A freshly started SDK with no purposes collects nothing — it installs its handlers and then drops every event. That's a deliberate, privacy-protective default: you decide the policy for your users; Pharen provides the mechanism to honour it.
Granting consent
Grant purposes at build time for classes you process from the first launch (crash reporting under legitimate interest is the common case). List them in Info.plist:
<key>PharenConsentGranted</key>
<array>
<string>telemetry</string>
<string>behavioral</string>
</array>Or grant them at runtime — for example, after your consent banner returns. Grants and revocations take effect immediately:
// Replace the whole granted set (e.g. from a consent banner result):
Pharen.shared?.consent.setConsent(granted: [.telemetry, .behavioral])
// Or change one purpose at a time:
Pharen.shared?.consent.grant(.behavioral)
Pharen.shared?.consent.revoke(.behavioral)Personal data never touches the event stream
When you identify a user, you pass a pseudonymous id and, optionally, traits:
Pharen.shared?.identify("cus_482", traits: ["plan": "pro"])The event stream carries only the pseudonymous customer id. Traits — and any device attributes you pass to identifyDevice() — route to a restricted attribute store, separate from events. They are never inlined into an event's properties, and they are never written to logs. The properties bag on track() is guarded the same way: obvious identifying keys are dropped before sending, and the server enforces the rule regardless.
Your tenant is stamped by the server, not your app
The SDK never sends your tenant_id or your environment. Both — along with your app id, when you use a per-app key — are derived on the server from the ingest key the request authenticates with. The key is public by design (it ships in your binary and carries no read access); the isolation boundary is the server-side derivation, which can't be spoofed from the client. That's also what makes the rings unspoofable: a Debug build physically cannot write to your production data.