Event Delivery & Data Durability
This page is the precise promise Crossdeck makes about your analytics events — how they queue, how they deliver, what survives a crash or an outdated SDK, and where the edges are. It is written so the bounds are numbers, not adjectives. And it draws one line clearly: analytics events are durable best-effort with honest bounds; your money — payments, subscriptions, entitlements — never travels through this queue at all.
Normal operation
Every SDK queues events on-device and delivers them in batches — flushing every few seconds or when the buffer fills, whichever comes first. Network failures re-queue the batch and retry with backoff; duplicate delivery is impossible because the server deduplicates on a per-event idempotency key, so a retry after a mid-flight crash never double-counts. On platforms with durable storage the queue is persisted on every enqueue: a crash, a force-quit, or a restart picks up exactly where it left off on the next launch.
Per-platform durability
| SDK | Queue lives in | Survives restart? | Bound |
|---|---|---|---|
@cross-deck/web | localStorage | Yes — across page loads and browser restarts | most recent ~1,000 events / device |
@cross-deck/react-native | AsyncStorage | Yes — across app restarts | most recent ~1,000 events / device |
@cross-deck/swift | on-disk queue | Yes — across relaunches | most recent ~1,000 events / device |
@cross-deck/node | process memory | No — a process restart clears the queue | held within the process lifetime |
await server.shutdown()), so the at-risk window is
small — but it is real, we say so, and an opt-in disk-backed queue
is on the roadmap (tracked internally; the SDK changelog will
announce it).
PARK: what happens on a version rejection
If your installed SDK is too old for the current event format, the
server refuses the batch with HTTP 426 Upgrade
Required (sdk_version_unsupported).
Your SDK recognises that exact response and parks —
a third outcome, deliberately distinct from retry (this
isn't transient) and drop (your data isn't invalid; only
the wire dialect is old):
- Holds the events in the durable on-device queue — nothing is discarded.
- Stops retrying a format it knows the server won't accept — no wasted battery, bandwidth, or noise.
- Warns you once — one console line naming the version to update to, plus the amber advisory in your dashboard.
- Ends on your upgrade — ship a build with a current SDK and the next launch backfills automatically, every event filed under its true occurrence time, not the upgrade time. No restore step, no replay button.
The scariest sentence — "your data stopped" — is actually "your data is waiting, and we told you immediately."
The bounds, in plain numbers
- Each device holds the most recent ~1,000 events. Beyond that the queue evicts the oldest first (FIFO) — recent activity is always what's preserved.
- In practice, for an active user that is roughly the last 1–2 days of activity. The longer a park window stays open, the more of the older tail ages out.
- Node: in-memory only (table above) — a process restart before you upgrade clears parked events. Web, React Native, and Swift hold theirs across restarts.
The contract, in one sentence
"One line" is literal: npm install @cross-deck/web@latest
(or a SwiftPM bump) and ship. The advisory in your dashboard names
the exact minimum version.
What's never at risk
Payments, subscriptions, and entitlements never travel through this queue. Money moves on server-side rails — Stripe, Apple, and Google report directly to Crossdeck's backend via signed webhooks, with their own retry semantics, idempotency keys, and reconciliation sweeps that re-check the rail's records against ours. A parked analytics queue, a full device, an outdated SDK, a phone at the bottom of a lake: none of it can lose a payment record or flip an entitlement.
That is the line this page exists to draw: money is sacred and multiply-guarded; analytics is durable best-effort with honest, numeric bounds. The two never share a failure mode.