Sync & Data Retention
Overview
Section titled “Overview”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
1. Get a license key
Section titled “1. Get a license key”Sign up at dashboard.presage-kit.dev, create a project, and copy your license key.
2. Configure your client
Section titled “2. Configure your client”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 ],})3. Configure allowed domains
Section titled “3. Configure allowed domains”In the dashboard, go to your project settings and add your production domains. Localhost is always allowed for development.
How sync works
Section titled “How sync works”Presage uses a batch-and-flush strategy to minimize network overhead:
- Batching — events, trait changes, and signal updates are queued in memory rather than sent individually.
- Flush interval — every 10 seconds (configurable via
batchIntervalMs), the queue is flushed as a single HTTP request tohttps://api.presage-kit.dev/api/v1/ingest. - Size-based flush — if the queue reaches
maxBatchSize(default 100) before the timer fires, it flushes immediately. - 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
maxRetriesattempts. - 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.
- Destroy flush — when
client.destroy()is called, a final flush is attempted so no data is lost on page unload.
Sync status
Section titled “Sync status”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).
Security
Section titled “Security”Presage uses a domain allowlist to prevent license abuse:
- Every sync request includes an
X-Originheader with the currentwindow.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.
localhostand127.0.0.1are 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.