@active-reach/web-sdk
v1.47.3
Published
Web SDK for Active Reach Intelligence — event tracking, identity resolution, in-app messaging, web push, and placements
Maintainers
Readme
@active-reach/web-sdk
Web SDK for Active Reach Intelligence — lightweight event tracking, identity resolution, in-app messaging (overlays, embedded cards, interactive widgets), and web push.
12KB gzipped | <1KB async loader | Zero impact on page load
Prerequisites
Before integrating, you need:
- Write Key — Retrieve from your Active Reach Intelligence dashboard under Settings > Developer > API Keys.
- API Host — Your cell endpoint URL (provided during onboarding).
Note: The write key is a publishable identifier (like a Stripe publishable key). It is safe to include in client-side code. It can only send events — it cannot read data, modify configuration, or access other tenants. Never use your Secret Key in client-side code.
Installation
NPM (Recommended)
npm install @active-reach/web-sdkCDN (Async Loader)
Add this snippet to your <head> tag. The async loader queues all calls until the full SDK loads — zero blocking.
<script>
!function(){var e=window.aegis=window.aegis||[];if(!e.invoked){e.invoked=!0,e.methods=["init","track","identify","page","group","alias","reset","flush","use","debug","setCell","getCellInfo","getAnonymousId","getUserId","getSessionId","setConsent","grantConsent","denyConsent","hasConsent","getConsentPreferences","onConsentChange"],e.factory=function(t){return function(){var n=Array.prototype.slice.call(arguments);return n.unshift(t),e.push(n),e}};for(var t=0;t<e.methods.length;t++){var n=e.methods[t];e[n]=e.factory(n)}e.load=function(t,n){var o=document.createElement("script");o.type="text/javascript",o.async=!0,o.src=n&&n.cdnUrl?n.cdnUrl:"https://cdn.activereach.ai/sdk/aegis.min.js",o.onerror=function(){window.console&&console.error("Aegis SDK failed to load")};var r=document.getElementsByTagName("script")[0];r.parentNode.insertBefore(o,r),e._loadOptions={key:t,options:n}},e.SNIPPET_VERSION="1.0.0"}}();
aegis.load('YOUR_WRITE_KEY');
aegis.init('YOUR_WRITE_KEY', {
api_host: 'https://YOUR_CELL.activereach.ai'
});
</script>Quick Start
NPM / ES Modules
import aegis from '@active-reach/web-sdk';
await aegis.init('YOUR_WRITE_KEY', {
api_host: 'https://YOUR_CELL.activereach.ai',
auto_page_view: true,
});
// Track an event
aegis.track('Purchase Completed', {
order_id: 'ORD-123',
revenue: 99.99,
currency: 'INR',
});
// Identify a user
aegis.identify('user_123', {
email: '[email protected]',
name: 'Priya Sharma',
plan: 'premium',
});React
import { AegisProvider, useAegis, useTrackEvent } from '@active-reach/web-sdk/react';
function App() {
return (
<AegisProvider
writeKey="YOUR_WRITE_KEY"
config={{ api_host: 'https://YOUR_CELL.activereach.ai', auto_page_view: true }}
>
<Dashboard />
</AegisProvider>
);
}
function Dashboard() {
const { isReady } = useAegis();
const trackEvent = useTrackEvent();
return (
<button onClick={() => trackEvent('Button Clicked', { button: 'CTA' })}>
Track Event
</button>
);
}Next.js
// app/providers.tsx
'use client';
import { AegisProvider } from '@active-reach/web-sdk/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<AegisProvider
writeKey={process.env.NEXT_PUBLIC_AEGIS_WRITE_KEY!}
config={{ api_host: process.env.NEXT_PUBLIC_AEGIS_API_HOST! }}
>
{children}
</AegisProvider>
);
}Configuration
await aegis.init('YOUR_WRITE_KEY', {
// Required
api_host: string;
// Batching & Performance
batch_size?: number; // default: 10
batch_interval?: number; // default: 1000ms
// Data Capture
capture_utm?: boolean; // default: true
capture_referrer?: boolean; // default: true
auto_page_view?: boolean; // default: false
// Session
session_timeout?: number; // default: 1800000 (30 min)
// Privacy & Consent
respect_dnt?: boolean; // default: true
enable_consent_mode?: boolean; // default: false
wait_for_consent?: boolean; // default: false
default_consent?: {
analytics?: boolean; // default: false
marketing?: boolean; // default: false
functional?: boolean; // default: true
};
integrate_onetrust?: boolean;
integrate_cookiebot?: boolean;
integrate_google_consent_mode?: boolean;
// Cookie
cross_domain_tracking?: boolean; // default: false
cookie_domain?: string;
secure_cookie?: boolean; // default: true
// Offline & Retry
enable_offline_mode?: boolean; // default: true
max_offline_events?: number; // default: 100
retry_failed_requests?: boolean; // default: true
max_retries?: number; // default: 3
// Regional Cell Selection
cell_endpoints?: CellEndpoint[];
preferred_region?: string;
auto_region_detection?: boolean; // default: true
// Debug
debug?: boolean; // default: false
});Core API
aegis.track(eventName, properties?)
Track a custom event.
aegis.track('Product Viewed', {
product_id: 'SKU-12345',
product_name: 'Leather Jacket',
price: 199.99,
category: 'Outerwear',
});aegis.identify(userId, traits?)
Identify a user and associate traits.
aegis.identify('user_123', {
email: '[email protected]',
name: 'John Doe',
plan: 'premium',
});aegis.page(name?, properties?)
Track a page view.
aegis.page('Product Page', { product_id: 'SKU-12345' });aegis.group(groupId, traits?)
Associate a user with a group (company, organization).
aegis.group('company_789', {
name: 'Acme Corp',
industry: 'Technology',
employees: 500,
});aegis.alias(newUserId)
Merge two user identities.
aegis.alias('permanent_user_id');aegis.reset()
Clear user identity on logout. Generates a new anonymous ID.
aegis.reset();aegis.flush()
Force-send all queued events immediately.
await aegis.flush();E-Commerce Tracking
Built-in helpers for standardized e-commerce events via aegis.ecommerce:
import aegis from '@active-reach/web-sdk';
// Product discovery
aegis.ecommerce.productViewed({
product_id: 'SKU-123',
name: 'Leather Jacket',
price: 4999,
currency: 'INR',
category: 'Outerwear',
brand: 'Acme',
variant_id: 'black_xl',
variant_label: 'Black XL',
});
aegis.ecommerce.productListViewed({
list_name: 'New Arrivals',
category: 'Outerwear',
products: [{ product_id: 'SKU-123', name: 'Leather Jacket', price: 4999 }],
});
aegis.ecommerce.searchPerformed({ query: 'leather jacket', results_count: 12 });
// Cart
aegis.ecommerce.addToCart({
product_id: 'SKU-123',
name: 'Leather Jacket',
price: 4999,
quantity: 1,
});
aegis.ecommerce.removeFromCart({ product_id: 'SKU-123', name: 'Leather Jacket', price: 4999 });
aegis.ecommerce.cartViewed({
cart_id: 'cart_456',
value: 4999,
products: [{ product_id: 'SKU-123', name: 'Leather Jacket', price: 4999, quantity: 1 }],
});
// Checkout
aegis.ecommerce.checkoutStarted({
value: 4999,
products: [{ product_id: 'SKU-123', name: 'Leather Jacket', price: 4999, quantity: 1 }],
coupon: 'WELCOME10',
shipping: 99,
tax: 450,
});
aegis.ecommerce.checkoutStep(2, { payment_method: 'UPI' });
// Order
aegis.ecommerce.orderCompleted({
order_id: 'ORD-789',
value: 5548,
revenue: 4999,
shipping: 99,
tax: 450,
payment_method: 'UPI',
products: [{ product_id: 'SKU-123', name: 'Leather Jacket', price: 4999, quantity: 1 }],
});
aegis.ecommerce.orderRefunded('ORD-789', 4999);
// Coupons
aegis.ecommerce.couponApplied({ coupon_code: 'WELCOME10', discount_value: 500, discount_type: 'fixed' });
aegis.ecommerce.couponRemoved({ coupon_code: 'WELCOME10' });
// Wishlist
aegis.ecommerce.wishlistItemAdded({
product: { product_id: 'SKU-123', name: 'Leather Jacket', price: 4999 },
});
// Promotions
aegis.ecommerce.promotionViewed({ name: 'Summer Sale', promotion_id: 'promo_1', creative: 'banner_hero' });
aegis.ecommerce.promotionClicked({ name: 'Summer Sale', promotion_id: 'promo_1' });All E-Commerce Methods
| Method | Event Name | Description |
|--------|-----------|-------------|
| productViewed(product) | product_viewed | User views a product page |
| productListViewed(list) | product_list_viewed | User views a category/collection |
| searchPerformed(search) | search_performed | User searches the catalog |
| addToCart(product) | cart_item_added | Item added to cart |
| removeFromCart(product) | cart_item_removed | Item removed from cart |
| cartViewed(cart) | cart_viewed | User views their cart |
| checkoutStarted(checkout) | checkout_started | Checkout flow begins |
| checkoutStep(step, options?) | checkout_step | Checkout step completed |
| orderCompleted(order) | order_completed | Purchase completed |
| orderRefunded(orderId, value?, products?) | order_refunded | Order refunded |
| couponApplied(coupon) | coupon_applied | Coupon/discount applied |
| couponRemoved(coupon) | coupon_removed | Coupon removed |
| wishlistItemAdded(wishlist) | wishlist_item_added | Item added to wishlist |
| promotionViewed(promo) | promotion_viewed | Promotion impression |
| promotionClicked(promo) | promotion_clicked | Promotion clicked |
React Hooks
Import from @active-reach/web-sdk/react:
| Hook | Returns | Description |
|------|---------|-------------|
| useAegis() | { aegis, isReady } | Access SDK instance and readiness state |
| useTrackEvent() | (name, props?) => void | Memoized event tracking callback |
| usePageView(name?, props?, deps?) | — | Track page views on mount/dependency change |
| useIdentifyUser() | (userId, traits?) => void | Memoized identify callback |
Consent Management
Built-in support for GDPR/CCPA compliance.
// Set preferences
aegis.setConsent({ analytics: true, marketing: false, functional: true });
// Grant / deny
aegis.grantConsent(); // all categories
aegis.grantConsent('analytics'); // specific category
aegis.denyConsent();
aegis.denyConsent('marketing');
// Query
aegis.hasConsent('analytics'); // boolean
aegis.getConsentPreferences(); // full object
// Listen for changes
const unsubscribe = aegis.onConsentChange((prefs) => {
console.log('Consent updated:', prefs);
});Third-Party Integrations
// OneTrust
aegis.init('KEY', { enable_consent_mode: true, integrate_onetrust: true });
// Cookiebot
aegis.init('KEY', { enable_consent_mode: true, integrate_cookiebot: true });
// Google Consent Mode v2
aegis.init('KEY', { enable_consent_mode: true, integrate_google_consent_mode: true });Web Push Notifications
Import from @active-reach/web-sdk/push:
import { AegisWebPush } from '@active-reach/web-sdk/push';
const push = new AegisWebPush({
writeKey: 'YOUR_WRITE_KEY',
apiHost: 'https://YOUR_CELL.activereach.ai',
vapidPublicKey: 'YOUR_VAPID_PUBLIC_KEY',
contactId: 'user_123',
organizationId: 'org_456',
serviceWorkerPath: '/aegis-sw.js',
});
await push.initialize();Copy dist/aegis-sw.js to your public/ directory so the service worker is served at the root.
In-App Messaging & Widgets
Every in-app format — modals, banners, tooltips, full-screen takeovers, bottom sheets, and interactive widgets (spin-the-wheel, scratch cards, NPS/CSAT surveys, quizzes, countdown offers, star ratings, polls, multi-step forms) — is delivered through a single entry point: AegisMessageRuntime.
import { AegisMessageRuntime } from '@active-reach/web-sdk';
const runtime = new AegisMessageRuntime({
writeKey: 'YOUR_WRITE_KEY',
apiHost: 'https://YOUR_CELL.activereach.ai',
contactId: 'user_123', // optional — set once the visitor is known
organizationId: 'org_456',
propertyId: 'prop_789', // the SDK install (website/app) this page is
});
await runtime.initialize();Migrating from an older version?
AegisInAppManager,AegisWidgetManager, andAegisPlacementManagerare no longer part of the public API —AegisMessageRuntimeis the one supported entry point and renders all formats. The standalone "placement" system has been removed; inline content is now delivered as embedded cards (see below).
Campaign content, audience, schedule, and frequency caps are configured server-side in the dashboard (In-App Personalization). The SDK evaluates client-side triggers (scroll depth, time on page, exit intent, inactivity, custom events) and renders the matching campaign automatically.
Drive triggers & identity from your app
runtime.track('added_to_cart'); // fire event-based display triggers
runtime.notifyConversion('purchase'); // suppress a campaign after it converts
await runtime.updateContactId('contact_42'); // re-evaluate once the visitor signs inPlacement — how a campaign reaches the customer
Each campaign picks one or more delivery modes in the dashboard's Placement tab. They are not mutually exclusive:
| Delivery mode | What it is | What you do on your site |
|---|---|---|
| Pop up (overlay) | A modal / banner / etc. the SDK renders over the page at runtime | Nothing — runtime.initialize() shows it when the trigger matches |
| Embedded card | The campaign renders inline, inside a slot you place on the page | Add an empty <div data-aegis-slot="…"> where you want it |
| Standalone page | The campaign gets its own tokenized URL (great for SMS / email / QR) | Link to the URL shown in the dashboard |
Embedded cards (inline placement)
Drop an empty element wherever you want the card to appear. Its data-aegis-slot value must equal the campaign's category (widget_category in the dashboard). On load, the SDK fills the first eligible campaign for that category:
<!-- Renders the active "promo" campaign here, inline in your page -->
<div data-aegis-slot="promo"></div>No per-slot script and no manual registration — runtime.initialize() scans for every [data-aegis-slot] element and fills it. It re-scans on each SSE/poll refresh and skips slots it has already filled.
Slot shells — reserve the box before the campaign arrives
An empty slot grows when its campaign lands, which pushes the page down (layout shift), and the hero image request waits for the campaign fetch. The SDK fixes both with a shell: a placeholder of the right size, drawn into each empty slot before it fetches, which the campaign then fills in place. A slot that nothing fills collapses as soon as the fetch answers, so a box never shows and then disappears later.
The SDK takes each slot's shell from the first of these that has one:
- Inline shapes your page prints (server-rendered pages): the
shapesfromGET /v1/in-app/slot-shapes(below), in a script tag. When this tag is present, a slot missing from it is treated as "will not fill".<script type="application/json" data-aegis-slot-shapes>{"shapes":[ … ]}</script> - The slot's own attributes (any site, by hand):
<div data-aegis-slot="hero" data-aegis-slot-aspect="8/3"></div> <div data-aegis-slot="commerce" data-aegis-slot-min-height="272"></div>data-aegis-slot-aspecttakesw/h(orw:h).data-aegis-slot-min-heighttakes pixels or a CSS length (300,20rem,45vh). - The last visit's shapes: after every fetch, the SDK saves the winning campaigns' shapes in
localStorage. The key is the property plus the declared surface, screen and outlet (locationId), and saved shapes expire after 6 hours. If storage is blocked, this step is skipped.
Content you render yourself is left alone. If the slot already has children (a data-aegis-slot-default fallback, or your own <div data-aegis-slot-shell> child), the SDK draws nothing there. It replaces your shell with the campaign, or removes it when the slot will not fill, exactly as it does its own. The SDK never draws a shape marked may_be_empty from data (for example a buy-again rail for a first-time visitor). Only the slot's own attributes can reserve a box for one.
GET /v1/in-app/slot-shapes: for server renderers. It takes the same write-key headers as /v1/in-app/active (X-Aegis-Write-Key, optional X-User-ID / X-Contact-ID / X-Property-Id). The query parameters are property_id (used when the header is absent), surface, screen, page_url (path + search), location_id, device_type, geo and is_new_user. The response carries Cache-Control: private, max-age=60 and the body is {"shapes": [SlotShape, …]}, one shape per slot that will fill. A slot that is absent will not fill. Each campaign in /v1/in-app/active also carries its own slot_shape.
Hosts that print shells themselves. The ACS storefront renders them from display.inapp_slot_shapes. The Shopify theme app extension renders them from the shop metafield aegis.slot_shapes, which the cell keeps in sync (shopify-plugin/theme-app-extension/snippets/aegis-slot-shell.liquid). Both mark their shells data-aegis-slot-shell + data-aegis-shell-owner="host" and print the inline shapes, so the SDK adopts them as described above.
SlotShape:
| Field | Meaning |
|---|---|
| slot | The slot key (data-aegis-slot value = the campaign's widget_category) |
| campaign_id | The winning campaign |
| layout | hero, rail, grid (columns 2, or 1 for a list), bar (announcement bar / value bar) or card |
| aspect | Box aspect w/h. For a hero this is the desktop aspect (8/3, ≥768px) |
| aspect_phone | Hero only: the phone (<768px) aspect (chrome.aspect_ratio, or 1/1 when a slide has a phone image). Null means use min_height |
| min_height / max_height | Pixels / a CSS length (a hero is capped at 70vh) |
| card_aspect, card_count, title | Rails and grids: each card's image aspect, the number of cards, and the heading (null when absent) |
| may_be_empty | The slot may stay empty for this viewer (a rail with nothing to show, or a winner that depends on who is looking: an A/B test or holdout in shapes computed for all anonymous visitors). Draw no box from data alone. Such a shape carries no image or sizes |
| rotate_count | The slot rotates through this many campaigns. The shape is the first one's |
| image | Hero only: the first slide's image {src, src_phone, alt, aspect, aspect_phone, width, height}. Render it as <img fetchpriority="high"> so it is the page's LCP request |
The hero draws slide 0 as <img fetchpriority="high" decoding="async" width height> and lazy-loads the other slides. Rail card images carry decoding="async" plus explicit width/height, and fade in once decoded.
Cart state — one store, fed the CEP-standard way
Every cart-reactive surface — the free-delivery progress card, the standalone progress bar, cart-value targeting rules, and cart-recovery widgets — reads ONE cart store. You feed it the same way you'd feed WebEngage/CleverTap: by firing the ecommerce events you already fire. The SDK derives the live cart from them; no separate cart call.
// 1) Wire the ecommerce tracker into the cart store, once at boot:
runtime.bindEcommerce(aegis.ecommerce);
// 2) Fire ecommerce events as the shopper shops — the cart is derived from these:
aegis.ecommerce.addToCart({ product_id: 'p1', name: 'Cap', price: 499, quantity: 1 });
aegis.ecommerce.cartViewed({ value: 998, currency: 'INR', products: [/* … */] }); // authoritative snapshotPlatform installs need no event wiring — the plugin does it for you. Carts are detected and fed into the same store automatically:
| Platform | Detection method | Setup |
|----------|-----------------|-------|
| Shopify | /cart.js poll + cart:updated (plugin) | Install the Shopify app |
| WooCommerce | aegis_cart + WC Store API on cart events (plugin) | Install the WP plugin |
| Magento 2 | localStorage['mage-cache-storage'] | Install the extension |
| Custom / headless / mobile | Fire aegis.ecommerce.* (above) | runtime.bindEcommerce(...) |
Escape hatch — a headless host that doesn't emit ecommerce events can push the cart directly; this writes the same one store:
runtime.setCartState({ total: 149.99, itemCount: 2, currency: 'INR' });Plugin System
Extend the SDK with custom plugins:
import type { Plugin } from '@active-reach/web-sdk';
const myPlugin: Plugin = {
name: 'my-plugin',
version: '1.0.0',
async init(config) { /* called on SDK init */ },
async beforeEventCapture(event) {
// Modify or enrich events before capture
event.properties = { ...event.properties, enriched: true };
return event;
},
async afterEventCapture(event) {
// Post-capture logic (external forwarding, logging, etc.)
},
};
aegis.use(myPlugin);TypeScript Support
Full type definitions included. Extend for type-safe event tracking:
// types/aegis.d.ts
import '@active-reach/web-sdk';
declare module '@active-reach/web-sdk' {
interface TrackEventProperties {
'Purchase Completed': {
order_id: string;
revenue: number;
currency: string;
};
'Product Viewed': {
product_id: string;
product_name: string;
price: number;
};
}
}Cookies & Storage
| Name | Type | Purpose | Duration |
|------|------|---------|----------|
| aegis_anon_id | Cookie | Anonymous user identifier | 365 days |
| aegis_user_id | Cookie | Identified user ID | 365 days |
| aegis_session_id | Cookie | Current session ID | Session |
| aegis_session_time | Cookie | Last activity timestamp | Session |
| aegis_consent | Cookie | Consent preferences | 365 days |
| aegis_offline_queue | localStorage | Offline event queue | Persistent |
| aegis_active_cell | localStorage | Active regional cell | 7 days |
All cookies: SameSite=Lax, Secure (HTTPS only), domain-scoped (optional).
Content Security Policy (CSP)
If your site uses a Content Security Policy, add these directives:
script-src: 'self' https://cdn.activereach.ai;
connect-src: 'self' https://*.activereach.ai;
style-src: 'self' 'unsafe-inline';
img-src: 'self' https://*.activereach.ai data:;If using DOMPurify via CDN, also add:
script-src: https://cdnjs.cloudflare.com;Common error when CSP is missing: Refused to connect to 'https://...activereach.ai' — add the domain to connect-src.
Security
- Write Key: Publishable client-side identifier. Cannot read data or modify server state. Safe to embed in source code.
- HTML Sanitization: Uses DOMPurify when available with a strict allowlist. Falls back to text-only rendering.
- URL Validation: All URLs validated —
javascript:anddata:protocols blocked. - Consent: Events are dropped (not queued) when consent is denied and
wait_for_consentis enabled. - Transport: All API calls use HTTPS with bearer token authentication.
What NOT to track
Never pass sensitive data as event properties:
// Never do this
aegis.track('Login', { password: '...' });
aegis.identify('user_123', { ssn: '...', credit_card: '...' });The SDK does not filter or redact properties. You are responsible for ensuring no PII beyond what you intend to collect is included in event payloads.
Debugging
aegis.debug(true);
// Or via init
aegis.init('KEY', { debug: true });Console output:
[Aegis] Initializing SDK...
[Aegis] Event queued: track { button_name: 'Sign Up' }
[Aegis] Flushing 5 events
[Aegis] Batch sent successfullySubpath Exports
| Import Path | Description |
|-------------|-------------|
| @active-reach/web-sdk | Core SDK (track, identify, page, group, alias) |
| @active-reach/web-sdk/react | React Provider + hooks |
| @active-reach/web-sdk/push | Web Push notification manager |
| @active-reach/web-sdk/cdn | IIFE bundle for <script> tag (window.aegis) |
| @active-reach/web-sdk/snippet | Minified async loader snippet |
| @active-reach/web-sdk/service-worker | Push notification service worker |
Browser Support
Chrome 80+, Firefox 78+, Safari 14+, Edge 80+.
License
MIT
