Skip to content

Sync & Data Retention

By default, Presage stores all data locally in the browser (localStorage). This works great for single-device adaptation but data is lost when the user clears their browser.

With a Presage Pro license ($19/mo), you can sync events, traits, and signals to a backend for:

  • Cross-device persistence — user context follows them across browsers
  • Long-term data retention — events stored beyond the browser’s 30-day limit
  • Analytics dashboard — visualize user behavior, maturity distribution, and feature usage
  • Domain security — restrict your license to authorized domains only

Sign up at dashboard.presage-kit.dev, create a project, and copy your license key.

Pass a sync object to createAdaptiveClient with your license key:

import {
createAdaptiveClient,
createLocalStorageDriver,
} from '@presage-kit/core'
const client = createAdaptiveClient({
persistence: {
driver: createLocalStorageDriver('my-app'),
},
sync: {
license: 'pk_live_abc123…',
// Optional overrides (shown with defaults):
// batchIntervalMs: 10_000,
// maxBatchSize: 100,
// maxRetries: 3,
onSyncError: (error) => {
console.warn('[presage sync]', error.type, error.message)
},
},
rules: [
// …your adaptation rules
],
})

In the dashboard, go to your project settings and add your production domains. Localhost is always allowed for development.

Presage uses a batch-and-flush strategy to minimize network overhead:

  1. Batching — events, trait changes, and signal updates are queued in memory rather than sent individually.
  2. Flush interval — every 10 seconds (configurable via batchIntervalMs), the queue is flushed as a single HTTP request to https://api.presage-kit.dev/api/v1/ingest.
  3. Size-based flush — if the queue reaches maxBatchSize (default 100) before the timer fires, it flushes immediately.
  4. Offline resilience — if a flush fails due to a network error, events are put back in the queue and retried on the next interval. Presage uses exponential backoff (1s, 2s, 4s) up to maxRetries attempts.
  5. Graceful degradation — if the license is invalid or the domain is not allowed, sync is permanently disabled for the session. The core engine keeps working locally without interruption.
  6. Destroy flush — when client.destroy() is called, a final flush is attempted so no data is lost on page unload.

The client exposes a reactive syncStatus atom so you can reflect the sync state in your UI:

import type { SyncStatus } from '@presage-kit/core'
// SyncStatus = 'idle' | 'validating' | 'active' | 'degraded' | 'disabled'
if (client.syncStatus) {
client.syncStatus.subscribe((status: SyncStatus) => {
switch (status) {
case 'idle':
// Sync configured but not yet validated
break
case 'validating':
// License check in progress
break
case 'active':
// License valid — events are syncing
break
case 'degraded':
// Network error during validation — will retry
break
case 'disabled':
// License invalid or domain not allowed
break
}
})
}

When syncStatus is null, sync was not configured (Free tier).

Presage uses a domain allowlist to prevent license abuse:

  • Every sync request includes an X-Origin header with the current window.location.origin.
  • The backend compares this origin against the domains you configured in the dashboard.
  • If the origin does not match, the backend returns a 403 Forbidden and the client disables sync for the remainder of the session.
  • localhost and 127.0.0.1 are always allowed so development is never blocked.

The onSyncError callback receives a typed error object that distinguishes between auth (invalid license), forbidden (domain mismatch), rate_limit, network, and server errors — letting you handle each case appropriately.