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.

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 / content stamped on session.started when present on the URL at session boot.
  • Referrer: document.referrer stamped on session.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. anonymousId is a random UUID stored in localStorage, 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; anonymousId is 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: UserDefaults for identity + super-properties; encrypted on disk by the OS. The durable event queue persists to UserDefaults on every enqueue.
  • Auto-capture: session.started/.ended via UIApplication lifecycle observers; page.viewed via UIViewController.viewDidAppear swizzle (UIKit) — SwiftUI requires .crossdeckScreen("Name"); element.clicked via UIControl.sendAction + UIWindow.sendEvent swizzles — 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.xcprivacy is 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: SharedPreferences for identity + super-properties + durable event queue.
  • Auto-capture: session.started/.ended via ProcessLifecycleOwner; page.viewed via Activity.onResume swizzle — Compose requires CrossdeckScreen("Name") { ... } (the Activity-host is denylisted); element.clicked via Window.Callback wrap 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.json synchronously inside the uncaught-exception handler BEFORE forwarding to Crashlytics; replayed as $error.recovered on next launch.
  • Device data: android.os.Build fields (MODEL, VERSION.RELEASE, VERSION.SDK_INT), Locale, TimeZone, BuildConfig.VERSION_NAME. No AAID collection.
  • Network reachability: ConnectivityManager.NetworkCallback fires queue.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.ready promise resolves once AsyncStorage hydration completes; pre-hydration track() 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. email is preserved as-is (the explicit identity-resolution channel). traits are 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.

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_contextci / 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: boot or hot_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-noTrack accessibility 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.