Documentation

Offline & Retry

Queueing, retries, and unload delivery behavior

Queueing model

  • track() pushes events to an in-memory queue immediately
  • The SDK schedules a flush every ~1 second when queue is non-empty
  • analytics.flush() forces an immediate send attempt
analytics.track('checkout_started')
analytics.track('checkout_completed', { order_id: 'ord_123' })
analytics.flush()

Delivery limits

Sizes are UTF-8 bytes of the exact serialized request body, not string length. A character such as € counts as three bytes.

LimitValue
Events per request or beacon100
Foreground request body512 KiB (524,288 bytes)
Foreground requests in flight2; later batches wait for a free slot
Each unload beacon, including the envelope32 KiB (32,768 bytes)
All beacons from one unload flush48 KiB (49,152 bytes)
  • Foreground delivery uses an ordinary fetch, notkeepalive. Browsers share a 64 KiB quota across keepalive requests and beacons, and refuse larger bodies.
  • flush() resolves after every batch it started, including batches still waiting for a request slot, has finished.
  • An event whose own request would exceed 512 KiB can never be delivered. Ingest also rejects properties larger than 50,000 bytes. The SDK drops the event and calls onError with a message naming the limit. It is not retried or stored offline.

Retry behavior

Failed batches retry with exponential backoff + jitter using defaults:maxRetries: 3, baseDelay: 1000,maxDelay: 30000.

Status/ErrorBehavior
429Retries after Retry-After, or with backoff when it is absent, capped at maxDelay
5xxRetries with backoff
Network errorsRetries with backoff
Other 4xxNo retry

Offline persistence

  • Pending events are persisted under the project-scoped ra:<public-key>:offline_queue key
  • A companion ra:<public-key>:offline_ad_user_data marker prevents unsafe restoration after consent changes
  • Stored queue is restored when the client initializes
  • Restored events are batched again by the byte limits above
  • Storage is capped at 1000 events

Unload behavior

When the page is hidden or unloads, the SDK sends queued events withnavigator.sendBeacon, including the autocaptured$pageleave. Each beacon and the unload total stay within the limits above. The SDK persists events for a later flush or page load when:

  • They do not fit in the unload budget
  • A single event is too large for a beacon (ordinary fetch sends it later)
  • Beacon is unavailable, or sendBeacon returns false
  • They belong to a request still in flight, which unload may cancel

A replayed event keeps its original eventId, so ingest deduplicates it. The SDK does not use synchronous XHR.

Lifecycle helpers

const pending = analytics.getQueueSize()

analytics.shutdown() // persists pending events + removes listeners

Callbacks

const analytics = createClient({
  publicKey: 'pk_live_xxx',
  onError: (error, events) => {
    console.error(error.message, events.length)
  },
  onSuccess: (_events, receipt) => {
    console.log('newly accepted', receipt.accepted)
    console.log(
      'idempotent duplicates',
      receipt.duplicatesInBatch + receipt.duplicatesPreviouslyAccepted
    )
  },
})

Next Steps