SDK Data Collection Reference
The exhaustive list of what the Crossdeck SDK auto-captures from your end users, what the SDK transmits to Crossdeck's servers in the request envelope, and what schema each event fires under. Written for your privacy review team — one page, no clicks-to-another-page.
Two flows are addressed separately. Flow A — data the SDK ships on your behalf under our processor relationship (the DPA governs). Flow B — the narrow diagnostic telemetry where Crossdeck acts as independent controller (Privacy Policy §6 governs).
A1. Default capture — every SDK
When Crossdeck.start(...) succeeds, every SDK fires
the following events automatically with no further code, unless
you opt out (see §C). Each row names the event, the properties
captured, and the lawful purpose under your relationship with
your end user.
| Event | Properties captured | Purpose |
|---|---|---|
session.started |
sessionId (random UUID), anonymousId (device-scoped, regenerated on reset()), referrer / referrer_host (web), browser, os, locale, timezone, app_version (mobile), sdk_name, sdk_version. |
Anchor a unit of user engagement; compute time-on-app; measure session-level engagement. |
session.ended |
same identifiers; durationMs. |
Pair with session.started to compute session duration. |
page.viewed |
Web: url (cleaned of query string), path, title (document.title), referrer, pageviewId. UIKit: screen (class name, framework hosts denylisted), title (controller.title), restorationId. SwiftUI: only when .crossdeckScreen("Name") is applied — screen + title set to your label. Compose: only when CrossdeckScreen("Name") { ... } is applied — same shape. |
Populate the Pages dashboard; attribute non-pageview events to the current page within a session. |
element.clicked |
Web: tag, text (visible text, capped 128 chars), elementId, ariaLabel, role, href, selector (stable CSS selector for the element), viewportX/Y, pageX/Y. iOS: element (UIView class name), accessibilityLabel, accessibilityId, title (for UIButton), viewportX/Y. SwiftUI: only when .crossdeckTap("Name") is applied for iOS 16+ Buttons — label + text set to your name. Android: element (View class name), contentDescription, text (TextView/Button.getText), resourceId, viewportX/Y. Compose: labels resolved via descendant-search where available. |
Behavioural analytics on which CTAs your end users press. |
error.captured |
exceptionType (e.g. "TypeError", "NSException"), message (post PII-scrub), fingerprint (stable hash from stack), frames (parsed stack frames — file, line, function), breadcrumbs (last 50 SDK breadcrumbs). |
Error capture, fingerprinted grouping, paying-user impact attribution. |
purchase.completed / .refunded / .failed |
rail (stripe / apple / google), product identifier, currency, amount_cents, subscription_id, idempotent_replay (Boolean, whether this is a backend retry-cache hit). |
Revenue analytics; entitlement projection; refund attribution. |
entitlement.granted / .revoked |
entitlementKey (developer-defined string), source rail, expiry timestamp. |
Lifecycle visibility into who has access to what right now. |
A2. PII scrubbing applied before transmission
Every SDK applies pre-transmission scrubbing to event property values that match known PII patterns. Matched substrings are replaced with redaction tokens before the event is enqueued; the persisted queue therefore never holds raw PII even if the device crashes mid-flush.
- Email addresses →
<email> - Payment card numbers (13–19 digit runs with Luhn validation) →
<card> - (Roadmap) Phone numbers, US Social Security Numbers, IBAN — added as customer demand justifies (each addition is announced in the Privacy Policy change history).
Scrubbing applies to: track() event property values
(recursively across nested objects and arrays), identify()
trait values (excepting the email trait which is
preserved by design — it is the explicit identity-resolution
channel), error message text, breadcrumb message text. Scrubbing
does not apply to event names, the canonical
email trait, or property keys themselves.
Scrubbing is on by default and is not customer-configurable on v1; an enterprise-tier option to extend the pattern list with customer-supplied regexes is on the roadmap.
A3. Web SDK specifics
- Persistence:
localStorage(anonymousId, identity, super-properties, durable event queue). No third-party cookies set. First-party cookies set only when the developer enables them explicitly. - IP address: not collected by the SDK; the request's source IP is visible to our load balancer in the request envelope (see §A9).
- UTM capture:
utm_source / medium / campaign / term / contentstamped onsession.startedwhen present on the URL at session boot. - Referrer:
document.referrerstamped onsession.started; referrer-host extracted for the dashboard's source attribution. - Device fingerprinting: none. The SDK does not use Canvas, WebGL, AudioContext, or font-list fingerprinting.
anonymousIdis a random UUID stored inlocalStorage, identical-device-different-browser is treated as a different anonymous identity.
A4. Node SDK specifics
- Persistence: in-memory queue + optional file-backed durable queue (path configurable; default
./crossdeck-queue.json). No browser storage, no cookies. - Identity: server-side
identify()calls;anonymousIdis supplied by the caller, not minted by the SDK (Node is multi-tenant by nature; the caller scopes identity). - Auto-capture: uncaught exception + unhandled rejection capture (opt-in via
errorCapture: { onUncaughtException: true, onUnhandledRejection: true }). Express / Firebase Cloud Functions / AWS Lambda middleware helpers available. - Express middleware: when enabled, captures HTTP request method + path + status code + duration as auto-events. Does not capture request bodies, query strings, or headers by default — the developer must opt in property-by-property.
A5. Swift SDK specifics (iOS / iPadOS / macOS / tvOS / watchOS)
- Persistence:
UserDefaultsfor identity + super-properties; encrypted on disk by the OS. The durable event queue persists toUserDefaultson every enqueue. - Auto-capture:
session.started/.endedviaUIApplicationlifecycle observers;page.viewedviaUIViewController.viewDidAppearswizzle (UIKit) — SwiftUI requires.crossdeckScreen("Name");element.clickedviaUIControl.sendAction+UIWindow.sendEventswizzles — SwiftUI buttons on iOS 16+ require.crossdeckTap("Name")(the Metal text pipeline hides labels from UIKit introspection). - Performance vitals (opt-in via
enablePerformanceMonitoring: true): cold launch time (Process.systemStartTime), ANRs (CADisplayLink watchdog), frame jank (Choreographer-equivalent rollups). - NSException capture: opt-in via
captureUncaughtExceptions: true; chains with prior handler so coexistence with Crashlytics / Sentry is preserved. - App Store Privacy Manifest:
PrivacyInfo.xcprivacyis shipped in the SDK package, declaring the data types collected and the API uses that motivate them. Apple's submit-time review reads this; the manifest is the source of truth. - Device data:
UIDevice.model+systemVersion+localizedModel,Locale,TimeZone,Bundle.main.infoDictionary["CFBundleShortVersionString"]. No IDFA collection.
A6. Android SDK specifics
- Persistence:
SharedPreferencesfor identity + super-properties + durable event queue. - Auto-capture:
session.started/.endedviaProcessLifecycleOwner;page.viewedviaActivity.onResumeswizzle — Compose requiresCrossdeckScreen("Name") { ... }(the Activity-host is denylisted);element.clickedviaWindow.Callbackwrap with a hit-test descent for Compose Button labels. - Performance vitals (opt-in): cold-launch (
Process.getStartElapsedRealtime), ANR (main-thread watchdog, 5s threshold matching Android's own), frame jank (Choreographer 60s rollups: total / slow >16ms / frozen >700ms). - Crash-on-relaunch persistence (default-on): fatal exceptions persisted to
<filesDir>/crossdeck-fatal.jsonsynchronously inside the uncaught-exception handler BEFORE forwarding to Crashlytics; replayed as$error.recoveredon next launch. - Device data:
android.os.Buildfields (MODEL,VERSION.RELEASE,VERSION.SDK_INT),Locale,TimeZone,BuildConfig.VERSION_NAME. No AAID collection. - Network reachability:
ConnectivityManager.NetworkCallbackfiresqueue.flush()on offline→online transitions.
A7. React Native SDK specifics
- Persistence:
AsyncStorage(peer dependency) — durable event queue persists across launches and process restarts. - Auto-capture: pure JS SDK; no native modules of its own. Auto-events fire via JavaScript lifecycle hooks. On iOS / Android, the underlying native rendering layer's auto-capture (UIKit / fragment) does not run — the RN bridge is the only event source. For native auto-capture, install the platform-native SDK alongside.
- Identity: same surface as the Web / Node SDKs (
identify,reset,track,isEntitled). - Hydration race:
Crossdeck.readypromise resolves once AsyncStorage hydration completes; pre-hydrationtrack()calls are buffered and ship in order.
A8. Explicit-call APIs (what you choose to send)
Beyond auto-capture, the SDK exposes APIs the developer calls with their own data. The SDK does not pre-fill these — what you pass is what we receive (subject to PII scrubbing per §A2).
identify(userId, { email, traits })— links the current anonymous identity to a developer-supplied user ID.emailis preserved as-is (the explicit identity-resolution channel).traitsare scrubbed.track(eventName, properties)— your custom events. Property values scrubbed per §A2.group(groupType, groupKey, traits)— B2B grouping (Web / Node).syncPurchases(...)— forward a payment-rail receipt for server-side verification.captureError(error, { handled })— manual error capture.setConsent({analytics, errors})— flip consent state.
A9. HTTP request envelope
Every event-bearing HTTP request from the SDK carries the following envelope metadata at the network layer, beyond what is inside the event payload itself. This data reaches our load balancer for request handling.
| Field | Source | How we treat it |
|---|---|---|
| Source IP address | Request origin. | For the event ingestion path, the IP is used to derive country (via MaxMind-equivalent) and is then truncated for any application-layer logging. Country is stamped onto the event for the dashboard's geo views. For the SDK Diagnostic Telemetry endpoint, the IP is truncated at the edge to network prefix and never used for geolocation — see §B. |
| User-Agent | Browser / system header. | Parsed to derive browser / OS for the dashboard's device-attribution views. |
Crossdeck-Sdk-Version header |
Set by the SDK; identifies SDK name + version. | Used to apply version-specific server-side handling; logged for support escalations. |
Idempotency-Key header |
Set by the SDK on every batch. | Stripe-style server-side response cache for 24 hours; protects against double-processing on retries. |
| TLS handshake metadata | TLS layer. | Not fingerprinted; not logged beyond debugging traces. |
B. Diagnostic telemetry (independent-controller flow)
When the SDK detects a bank-grade contract failure during
installation, test runs, or production execution, it emits a
crossdeck.contract_failed event to Crossdeck for
our operational reliability purposes. We act as
independent controller for this telemetry; this
flow is not part of the DPA. See Privacy
Policy §6 for the full legal framework.
Payload — exhaustive list
The payload contents are locked by a contract in our public
contracts/ directory and enforced by per-SDK unit
tests in CI. Adding a field requires an open pull request,
contract update, and Privacy Policy amendment.
contract_id— identifier of the failed contract.sdk_version,sdk_platform.failure_reason— short machine-readable reason code.run_context—ci/dogfood/customer-app.run_id— opaque, not stable across runs, never linked to an end user.- Optional:
test_file,test_name,device_class,verification_phase(categorical:bootorhot_path).
The payload contains no end-user identifiers, no IP, no email, no free-form text, no exception messages, no stack frames.
Envelope
IP and User-Agent reach the endpoint at the network layer as
with any HTTP request. They are not persisted
beyond the request handling layer: IP is truncated to network
prefix (/24 IPv4, /48 IPv6) before application logging,
User-Agent is read for sdk_version envelope
validation then discarded, no geolocation derivation, no TLS
fingerprinting, operational logs retained 7 days only.
C. Per-feature opt-outs available to the developer
All auto-capture is configurable. The opt-out surface area:
autoTrack: .off— disables session, screen, and tap auto-capture entirely.track()still works.autoTrack: { sessions: false }/{ screenViews: false }/{ taps: false }— granular.errorCapture: { onUncaughtException: false }/{ wrapFetch: false }— opt out of error auto-wiring.initialConsent: .deny/setConsent({ analytics: false, errors: false })— runtime consent toggle for compliance flows.respectDnt: true— honour the legacy DNT header (off by default since DNT is W3C-deprecated; most browsers no longer send it).- Element-level
cd-noTrackaccessibility identifier — exclude a specific element (or its subtree) from autocapture. - SwiftUI / Compose: bolt-on modifiers are opt-in by design — auto-capture is silent on iOS-16+-SwiftUI / pure-Compose Buttons until you wire them.
- SDK Diagnostic Telemetry (Flow B): currently always on. Crossdeck is committed to operational telemetry that depends on its consistent receipt for reliability across all installs; the schema-lock contract is the structural guarantee that the payload remains diagnostic-only. Enterprise opt-out is on the roadmap.