triggair
Guides

Reliability: retries, idempotency and offline

How the SDK survives dropped connections and retries: the durable outbox, idempotency keys, and the error contract.

Games run on flaky mobile networks and tabs that sleep. The SDK is built so a dropped connection or a retried request never corrupts state: you write calls the obvious way and the durability is automatic.

Automatic retries with backoff

Transient failures (429 rate limited, 5xx, and network errors) are retried automatically with exponential backoff that honors a Retry-After header. A 401 on a player call triggers exactly one silent token refresh, then retries. You only see an error once retries are exhausted, and it's always a typed TriggairError.

The durable outbox

Analytics events and cloud saves flow through a durable outbox persisted in local storage. If the connection drops they replay on reconnect (and on an interval, and on the browser's `online` event). Events coalesce by name (counts summed) and saves coalesce by slot (last-write-wins), so a backlog flushes compactly. The flush is best-effort background work: it never throws into your game loop.

tg.track('level_complete');      // queued; survives a reload or offline spell
tg.saves.queue('slot1', data);   // durable save — replays on reconnect

Idempotency keys

Every outbox item carries a stable Idempotency-Key, so a replay after a failure resends the identical request under the identical key and the server dedupes it, so a batch is applied at most once. For grants you trigger directly (a purchase, a loot open, a gift), pass your own idem key so a double-tap or a client retry can't double-charge or double-grant.

await tg.economy.buy('main_store', listingId, { idem: uuid });
await tg.economy.loot.open('bronze_box', { idem: uuid });

At-least-once vs exactly-once

Analytics events are at-least-once: coalesced counts can over-deliver slightly, so never derive currency from a raw event count. Anything that grants value is exactly-once: it flows through the inbox and is granted only when the player claims it (see the Inbox and rewards guide). The split is deliberate: cheap telemetry is lossy-safe; money is audited.

The error contract

Every failure is a TriggairError with { code, message, agentHint, requestId }. code is stable and machine-checkable; agentHint tells you (or your AI) how to fix the call; requestId is what you quote in a bug report. Branch on code, never on message text.

try {
  await tg.economy.buy('main_store', id, { idem });
} catch (e) {
  if (e.code === 'insufficient_funds') promptTopUp();
}
More in Getting started

← All guides · Recipes · API reference