Skip to Content
DevelopersSDKsWeb SDKOverview

Web SDK

The Active Reach Web SDK tracks user behavior, identifies visitors, manages push notification subscriptions, renders in-app messages, and sends e-commerce events — all from the browser.

Current version: 1.14.1 — published through the Phase 5.6 release-readiness gate (the same drift + smoke contract the mobile SDKs go through).

Brand canon. The customer-facing package is @active-reach/web-sdk but the class you import is still Aegis. This is intentional and matches the rest of the family: “Aegis” is the internal source-tree name; “Active Reach” is the customer-facing identifier on registries (npm, CocoaPods, Maven, pub.dev) and customer-visible runtime strings (console prefixes, User-Agent headers, OS log subsystems).

Installation

Three ways to install, depending on your stack.

CDN snippet (fastest)

Drop this before </body> on every page. For SPAs, add once in your entry file.

<!-- latest --> <script src="https://cdn.activereach.ai/sdk/aegis.min.js"></script> <!-- or pin to a version (recommended for production) --> <!-- <script src="https://cdn.activereach.ai/sdk/v1.14.1/aegis.min.js"></script> --> <script> Aegis.init('YOUR_WRITE_KEY', { workspace_id: 'YOUR_WORKSPACE_ID', api_host: 'https://api.active-reach.ai' }); </script>

The CDN is fronted by Cloudflare. Latest-tagged assets have a 24-hour cache lifetime; pinned v{version} assets are immutable for one year. The publish workflow uploads three artefacts each release: aegis.min.js, snippet.min.js, aegis-sw.js (service worker for web push).

npm install @active-reach/web-sdk
import { Aegis } from '@active-reach/web-sdk'; await Aegis.init('YOUR_WRITE_KEY', { workspace_id: 'YOUR_WORKSPACE_ID', });

React hook

npm install @active-reach/web-sdk
import { AegisProvider, useAegis } from '@active-reach/web-sdk/react'; function App() { return ( <AegisProvider writeKey="YOUR_WRITE_KEY" config={{ workspace_id: 'YOUR_WORKSPACE_ID' }}> <YourApp /> </AegisProvider> ); } function TrackButton() { const aegis = useAegis(); return ( <button onClick={() => aegis.track('button_clicked', { label: 'signup' })}> Sign up </button> ); }

Core tracking methods

MethodDescription
track(event, properties?)Track a custom event (e.g., order_completed, page_scrolled)
identify(userId, traits?)Identify a user and set traits (name, email, plan, etc.)
page(name?, properties?)Track a page view — called automatically on load + SPA navigation if auto_page_view is enabled
group(groupId, traits?)Associate the user with a company or account
alias(newUserId)Merge an anonymous session with an identified user
reset()Clear identity and start a fresh anonymous session
flush()Manually flush queued events — call before window.unload

track

Aegis.track('order_completed', { order_id: 'ORD-123', amount: 4999, currency: 'INR', products: ['SKU-A', 'SKU-B'], });

identify

Aegis.identify('user_456', { name: 'Priya Sharma', email: '[email protected]', plan: 'pro', created_at: '2026-01-15', });

Call identify after login or signup. Traits merge — you don’t need to send every trait every time.

page

Aegis.page('Product Detail', { product_id: 'SKU-A', category: 'Electronics', });

If auto_page_view is enabled (default), page() fires automatically on every navigation. Call it manually only if you want to add custom properties.

group

Aegis.group('company_789', { name: 'Acme Corp', industry: 'Retail', employees: 50, });

alias

// After a user signs up, merge their anonymous browsing history Aegis.alias('user_456');

reset

// On logout — clears user ID, traits, and session Aegis.reset();

E-commerce tracking

The SDK includes purpose-built e-commerce methods that emit standardized events:

import { EcommerceTracker } from '@active-reach/web-sdk'; EcommerceTracker.trackProductView({ id: 'SKU-A', name: 'Widget', price: 999 }); EcommerceTracker.trackAddToCart({ id: 'SKU-A', name: 'Widget', price: 999, quantity: 1 }); EcommerceTracker.trackCheckoutStarted({ cart_id: 'CART-1', total: 999 }); EcommerceTracker.trackOrderCompleted({ order_id: 'ORD-1', total: 999, currency: 'INR' }); EcommerceTracker.trackSearch({ query: 'blue widget' });

Push notifications

import { AegisWebPush } from '@active-reach/web-sdk/push'; await AegisWebPush.init({ vapidPublicKey: 'YOUR_VAPID_KEY' }); const subscription = await AegisWebPush.subscribe(); AegisWebPush.onPushReceived((notification) => { console.log('Push received:', notification); }); AegisWebPush.onPushClicked((notification) => { console.log('Push clicked:', notification); });

Requires a service worker — see Push setup guide.

In-app messaging

In-app campaigns are fetched and rendered automatically once the SDK is initialized with inapp: { enabled: true }. No manual code needed for basic display.

For programmatic control, use the AegisMessageRuntime facade — it owns the in-app and widget managers as readonly props (the manager classes are intentionally not barrel-exported; consumers reach them through the runtime):

import { AegisMessageRuntime } from '@active-reach/web-sdk'; const runtime = new AegisMessageRuntime({ /* config */ }); await runtime.initialize(); runtime.inApp.show('campaign_123'); // Show a specific campaign runtime.inApp.dismiss(); // Dismiss the current message runtime.notifyConversion('checkout'); // Silence armed campaigns + journey // -queued cart-recovery touches (G1 seam)

New methods added on the underlying managers are reachable as runtime.<area>.<method> without any additional wiring.

Workspace-aware runtime (1.14.0)

On multi-outlet storefronts the SDK needs to know which outlet the customer is on so events stamp the right workspace_id. 1.14.0 introduces a resolution cascade so a single SDK install can disambiguate without per-outlet boilerplate:

config.workspace_id → ?ws=<id> → URL path slug → setWorkspace() → sessionStorage → undefined

The URL-path slug step matches the first segment of the path against the org’s workspaceCodes allowlist (delivered by the bootstrap call). On a customer-facing URL like slug.actii.me/south/rewards the SDK auto-detects south and stamps every event with that workspace’s UUID without any operator config.

Three new runtime methods support host-driven control:

aegis.setWorkspace('south'); // pin runtime workspace + persist in sessionStorage aegis.clearWorkspace(); // clear the runtime + sessionStorage step aegis.ingestWorkspaceCodes(['south', 'ghatkopar', 'bandra']); // seed allowlist when bootstrap is bypassed

The bootstrap response now carries workspaceCodes: string[] for the org so the SDK can validate path-segment matches client-side and skip non-workspace prefixes like /products or /collections.

In-app and widget plumbing. AegisInAppManager and AegisWidgetManager expose a getWorkspaceId config callback. The host SDK wires () => aegis.getEffectiveWorkspaceId() so impressions, clicks, dismisses, and widget interactions (spin/scratch plays, feedback submissions) carry the same workspace context the main analytics stream uses. Without it, in-app engagement events arrive at event-ingress unscoped and fall back to the org’s primary workspace.

Gateway-side normalization. The platform resolves workspace slugs to UUIDs server-side and the canonical event always stamps the UUID. The SDK can therefore send either a slug or a UUID — both forms work, and resolutions are cached so the lookup adds negligible latency.

User namespace and per-channel opt-in (1.13.0)

The aegis.user namespace is the WebEngage-/CleverTap-parity entry point for PII writes and per-channel marketing consent. Every method delegates to aegis.identify() under the hood with SDK-side validation:

aegis.user.login(userId, traits?); aegis.user.logout(); aegis.user.setEmail(email); // RFC 5322 simple validation aegis.user.setPhone(e164); // E.164 validation aegis.user.setHashedEmail(sha256hex); aegis.user.setOptIn(channel, granted); // channel: 'email' | 'sms' | 'push' // | 'webpush' | 'whatsapp' | 'rcs' | 'inapp'

setX calls before login() accumulate in an in-memory buffer and flush as one consolidated identify event when login() fires — avoids the anonymous-id-as-userId footgun.

Consent UI mirror requirement. Any cookie-consent or marketing-opt-in UI in your storefront must mirror its state to aegis.user.setOptIn(channel, granted) for every supported channel. Writing only to localStorage keeps the consent state invisible to segmenters, journey channel pickers, and the DPDP audit trail.

Client-side governance — TraitGovernor / NameGovernor

The aegis.user namespace runs every trait write through a TraitGovernor before the payload reaches the network. It mirrors the same ingestion guards the cell-plane backend enforces at the trait-write chokepoint — so developers see problems in the browser console the first time they happen, not in the Validation tab after a production batch.

What the governor does on every aegis.user.setX(...) / identify() / group() call:

  • Normalizes keys — camelCase / PascalCase / dashed / space-separated keys are rewritten to snake_case. firstName becomes first_name automatically.
  • Drops reserved-prefix keyssystem., user., loyalty., cart., bill., $, _, … belong to the platform. Writes against them are dropped with a reserved_prefix verdict.
  • Truncates long strings; rejects huge ones — soft cap truncates, hard cap rejects (DoS protection).
  • Parses date-keyed fields — values on *_at, *_on, created, expires, dob, … keys are auto-parsed from ISO-8601 / epoch strings to epoch-ms.

Drops surface as console.warn lines rate-limited to first 3 per (workspace_id, verdict) per session. The backend remains the authoritative chokepoint; the SDK path is advisory — designed to catch the issue early, not enforce harder than the backend.

For the full verdict matrix and the cross-SDK drift contract, see Event governance → TraitGovernor.

NameGovernor is the event-name counterpart — covered in § Event governance below.

Plugin registry

Aegis ships a typed plugin registry that any third-party integration can register against. The registry replaces the earlier ad-hoc hook subscription with a formal Plugin interface — plugins receive lifecycle hooks (init, beforeEventCapture, afterEventCapture, beforeBatchSend, afterBatchSend, onError, destroy) and can suppress (return null from beforeEventCapture) or transform events on their way to the gateway.

import { Aegis, type Plugin } from '@active-reach/web-sdk'; const myPlugin: Plugin = { name: 'my-analytics-bridge', version: '1.0.0', beforeEventCapture(event) { // Mutate or filter — return null to suppress. return event; }, }; Aegis.use(myPlugin);

Use the registry instead of monkey-patching Aegis.track. The same shape is consumed by every first-party integration, including the Meta Pixel plugin below.

Meta Pixel plugin

The Web SDK includes a first-party Meta Pixel bridge for ad-attribution parity. The SDK manages the _fbp and _fbc browser cookies (the same first-party match keys Meta uses for EMQ — Event Match Quality) and exposes aegis.lastEventId(eventName) so a tenant’s own Meta Pixel can pass the same event_id for client-side dedup with the server-side Conversions API.

CapabilityWhere it lives
_fbp cookie write + readlibs/web-sdk/src/utils/meta-cookies.ts — 90-day TTL, fb.1.<creation_ms>.<random> format
_fbc cookie write + readCaptured from ?fbclid=<id> on landing, persisted same TTL
aegis.lastEventId(name)Returns the messageId of the most recent track(<name>)null if the event hasn’t fired since init
Consent gatingHonors marketing consent — when denied, _fbp / _fbc are neither generated nor stamped onto events

Mobile parity ships as MetaPixelCompanion on iOS / Android / React Native / Flutter (Phase 4.5). The mobile companion is in-memory only; cookie equivalents are persisted in storage and stamped onto outgoing event metadata so the cell-plane Meta CAPI service has matching keys. The cross-SDK wire contract is pinned by libs/mobile-sdk/tests/drift/meta-pixel-companion-contract.json.

See E-commerce integrations → Meta Pixel for the operator-facing setup.

SPA navigation — aegis.screen()

Use aegis.screen('storefront.cart') for SPA-logical surface tracking (cart drawer, checkout step, in-app modal step) distinct from URL-bound page(). The gateway maps screen events to canonical event engagement.screen_view.

aegis.screen('storefront.cart', { cart_id: 'CART-1' });

Don’t ALSO call page() for SPA transitions — the canonical mapping is one-shot per surface change.

E-commerce — back-in-stock signup

productWaitlisted is the SDK-side counterpart to the cashier-portal <BackInStockButton> on PDP. The cell-side stock_event_handler_worker fans out to waitlisted contacts when a restock fires (older signups expire per the platform retention window):

aegis.ecommerce.productWaitlisted({ productId: 'SKU-A', name: 'Widget', email: '[email protected]', // or rely on identified user });

There’s no default catalog.back_in_stock journey template — operators wire their own using the standard journey builder.

Cart abandonment is server-side

Don’t roll your own JS timer for cart abandonment. The platform scans abandoned carts via Celery beat over ClickHouse (storefront_cart_abandonment_scanner) — same pattern as CleverTap / WebEngage / Klaviyo. JS timers miss tab-close, drain battery, and leak timing logic into the browser. The same pattern applies to search-, browse-, and checkout-abandonment.

Event governance

The Web SDK ships with client-side event-name cap enforcement (introduced in SDK 1.4.0 ). When you bootstrap the SDK, the server returns a compact Bloom filter of your already-registered event names plus a counter of how many new names you can still add. The SDK uses this to drop novel event names locally once you hit your plan cap, so a bug like track('view_' + id) doesn’t amplify into a thousand gateway round-trips.

Integration (two lines)

import { bootstrap } from '@active-reach/web-sdk'; const result = await bootstrap(apiHost, { writeKey, currentOrigin: window.location.origin }); await aegis.init(writeKey, { /* ... */ }); aegis.ingestGovernanceHint(result.eventGovernance); // ← this line

Skip the ingestGovernanceHint call and the SDK silently fails open — the gateway remains the authoritative cap, you just don’t get the local-drop optimization.

What the SDK does

  • Known name → sends immediately (Bloom hit)
  • Novel name, within headroom → sends; increments a per-instance counter; remembers the name so calling it again doesn’t double-charge
  • Novel name, over cap, grace inactive → drops locally; logs one console.warn per distinct dropped name; queues a coalesced aegis.client.name_governor_dropped meta event for the next flush so ops dashboards see the drop pattern
  • Grace window active (see Event governance concept → Grace period) → SDK fails open — gateway is still accepting novel names, so the SDK shouldn’t drop

The hint shape

export interface EventGovernanceHint { bloom_algo: 'mmh3_x86_32_km'; seed_a: 0; seed_b: 1; k: number; // hash count (typically 7) m: number; // bitarray size (power of 2, typically 1024) bloom_b64: string; // base64 of the bitarray, LSB-first within each byte remaining_new_names: number | null; // null = Enterprise / unlimited grace_active?: boolean; ttl_seconds: number; // SDK re-bootstraps if older than this }

The bloom_algo is versioned — if the server ships a new algo and your SDK doesn’t recognize it, the SDK silently disables governance and falls back to gateway enforcement. Safe by default.

Advanced — inspecting governor state

For debugging:

import { NameGovernor } from '@active-reach/web-sdk'; // Rarely needed — the governor is instantiated internally by Aegis. // Exposed for advanced use (custom bloom construction, test fixtures).

Full API reference for governance → Reference page

Configuration reference

Key config options passed to Aegis.init():

OptionTypeDefaultDescription
workspace_idstringRequiredYour workspace ID
api_hoststringAuto-detectedAPI endpoint (region-based)
auto_page_viewbooleantrueAutomatically track page views on navigation
batch_sizenumber50Events per batch before sending
batch_intervalnumber5000Milliseconds between batch sends
enable_offline_modebooleantrueCache events when offline
session_timeoutnumber1800000Session timeout in ms (30 min)
cookie_domainstringCurrent domainCookie domain for cross-domain tracking
wait_for_consentbooleanfalseDefer event sending until consent granted
enable_consent_modebooleanfalseEnable GDPR/CCPA consent mode
respect_dntbooleantrueHonor browser Do Not Track signal
rate_limit_per_secondnumber100Client-side event rate limit
debugbooleanfalseEnable console debug logging

Full config reference with all 40+ options → SDK Config Reference (coming soon)

What’s next

Geofencing is a mobile-only capability (iOS CoreLocation / Android Play Services) — there is no Web SDK equivalent. Use the React Native or Flutter geofencing modules in a mobile app.