# Event Delivery & Data Durability

> Source: https://cross-deck.com/docs/sdk-event-durability/ · Crossdeck developer docs (generated from the published page).

SDKs
4 min read
·
The honest contract behind every event you send

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

Node's caveat, stated plainly. The server SDK's
queue is in-memory today: events buffered between flushes are lost
if the process dies before they deliver. Long-running servers
flush every 1.5 seconds by default and drain on shutdown
( 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

Crossdeck guarantees delivery of queued events across
normal failures; during a version-rejection window we preserve the
most recent ~1,000 events per device — the longer the window, the
more history ages out. Keep SDKs current; we make that one
line.

"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.
