triggair
Guides

Reliability: retries, idempotency & 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 — 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 & 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