churn-warn-js-sdk
v0.7.0
Published
Browser SDK for POST /api/events to ChurnWarn Gateway.
Maintainers
Readme
ChurnWarn JavaScript browser SDK v0.7.0
Small, dependency-free client for sending raw product events to the ChurnWarn Gateway POST /api/events endpoint. Intended for script tag embedding (bundled as a single global).
v0.7.0 additions (non-breaking)
- Tier-C signals —
Metricsnow covers every business-type template signal (order_placed,product_viewed,cart_created,iap_purchase,transaction, …). NewPayloadFields,AccountAttributes,BusinessTypesconstants (mirrorsdks/signals.manifest.json).data-cw-signal— tag any element and auto-capture emits that business signal on click. See Domain signals for dashboard templates.- E-commerce bridge —
autoCapture: { ecommerce: true }mirrors GA4/GTMdataLayer+gtagecommerce events to ChurnWarn signals with zero extra code.identify(id, traits)/upsertAccount(id, traits)— write account facts (plan, LTV,push_opt_in, marketplacerole, …) toPUT /api/accounts.
v0.6.0 breaking changes
element_clicked.<label>event types are gone. All clicks now emit a singleelement_clickedtype withpayload.labelfor the button/link name. Update any event-map patterns fromelement_clicked.*→element_clicked.page_viewedpayload now includes aroutefield: the pathname with auto-collapsed IDs (UUIDs, integers, long tokens) replaced by:id. Use in condition mappings:{"payload.route":"/orders/:id"}.AutoCaptureEvents.ELEMENT_CLICKED_PREFIXrenamed toAutoCaptureEvents.ELEMENT_CLICKED.
What you get
configure— set bearer token, optional API base URL, optional tenant override, optional event source label, optional auto-capture.identify— set the account id for auto-capture (call after login).captureEvent— fire-and-forget HTTP POST; does not block the UI thread after the call returns.Metrics— frozen object of canonical event type strings (aligned with the default dashboard metrics in the API).RawEvents— frozen object of dotted raw event names the API can map to those metrics.AutoCaptureEvents— frozen event names emitted by auto-capture (page_viewed,session_started, etc.).version— SDK version string.
Runtime has no npm dependencies. The published file is an ES5-targeted IIFE exposing the global ChurnWarn.
Quick start (script tag)
Host or copy dist/churn-warn.umd.min.js and load it before your app code:
<script src="/static/churn-warn.umd.min.js"></script>
<script>
ChurnWarn.configure({
apiKey: '<your-ingest-api-key>', // from ChurnWarn Settings → API keys
serverUrl: 'https://your-gateway.example.com', // omit if same-origin
tenantId: '00000000-0000-0000-0000-000000000000',
autoCapture: true // page views, clicks, sessions, errors
});
// Call after the user is authenticated and you know their account id
ChurnWarn.identify('account-external-id-123');
</script>That's all you need. Auto-capture handles page views, session tracking, clicks, errors, and scroll depth automatically once identify() is called.
Call configure and identify as early as possible (ideally before your app paints) so session and page events are not missed.
Auto-capture
When autoCapture is enabled, the SDK emits health-oriented events automatically after identify(accountId):
| Event | When |
|-------|------|
| page_viewed | Initial identify + every URL change — payload includes url (origin + path only), path, and route (normalized) |
| session_started | New session (first visit or idle longer than timeout) |
| session_ended | Tab hidden or page unload (session_minutes in payload) |
| session_heartbeat | Every 60s while the tab is visible and the user was active in the last 90s |
| element_clicked | Clicks on buttons, links, inputs, and labeled elements — payload.label has the element name |
| feature_used | Any click inside a data-feature="…" section |
| rage_click | 3+ clicks on the same element within 2 s |
| form_abandoned | Input typed into but form never submitted |
| scroll_depth | 25 / 50 / 75 / 100 % scroll milestones |
| js_error | Uncaught errors and unhandled promise rejections |
Route normalization
page_viewed payloads include a route field — the pathname with high-cardinality ID segments auto-collapsed to :id:
/orders/9281 → /orders/:id
/users/abc-123/profile → /users/:id/profile
/static/home → /static/home (no collapse — not ID-like)Use this in ChurnWarn event-map conditions to route page views to metrics:
page_viewed → feature_used condition: {"payload.route":"/billing*"}
page_viewed → plan_upgraded condition: {"payload.route":"/checkout/success"}Override or extend the auto-collapse rules with routes.normalize:
ChurnWarn.configure({
autoCapture: {
enabled: true,
routes: {
normalize: [
{ pattern: '/reports/*', as: '/reports/:id' },
{ pattern: '/orgs/*/members', as: '/orgs/:id/members' }
]
}
}
});User-defined rules are evaluated first; the auto-collapse (UUID / integer / long-hex) runs as a fallback.
Privacy and sensitive data
Auto-capture is designed to avoid leaking query strings, URL fragments, and raw DOM text:
urlandreferrerinclude onlyorigin + pathname. Query parameters and hash fragments are never sent.element_clickeddoes not capture visibleinnerText/textContent. Usedata-churn-warn-labelfor stable click names.- Click labels derived from
data-testid,id,aria-label, ornameare masked for common sensitive patterns (emails, tokens, phone numbers, etc.). - Mark sensitive UI with
data-churn-warn-ignoreto exclude it from click capture entirely. - Prefer
payload.route(not full URLs) in event-map conditions.
Configuration
// Shorthand — enables all auto-capture with defaults
ChurnWarn.configure({ token: '...', autoCapture: true });
// Full options object
ChurnWarn.configure({
token: '...',
autoCapture: {
enabled: true,
trackClicks: true, // default true
sessionTimeoutMinutes: 30, // default 30; new session after this idle gap
captureErrors: true, // default true
captureRageClicks: true, // default true
captureFormAbandons: true, // default true
captureScrollDepth: true, // default true
captureFeatures: true, // default true
routes: {
normalize: [] // custom route rules (see above)
}
}
});
// Disable and tear down listeners
ChurnWarn.configure({ token: '...', autoCapture: false });Auto-capture attaches listeners when enabled but does not send events until identify(externalAccountId) is called. Re-calling identify with the same id is a no-op; a different id ends the prior session and starts fresh.
Click labeling
Use attributes on interactive elements:
data-churn-warn-label="submit_checkout"— preferred stable name (also makes non-standard elements clickable).data-churn-warn-ignore— exclude element and descendants from click capture.
Otherwise the SDK derives a name from data-testid, id, aria-label, name, or a tag_role fallback. Visible button/link text is not captured. Names are sanitized to [a-z0-9_] and masked for common sensitive patterns.
Domain signals for dashboard templates
The auto-capture table above feeds the default SaaS/PLG dashboard. For the business-type
templates (e-commerce, subscription-box, fintech, mobile, marketplace) you also need their
domain signals — order_placed, product_viewed, cart_created, transaction, etc. Two
zero-to-low-code ways to emit them from the browser:
1. data-cw-signal attributes (declarative)
Tag any element. On click, auto-capture emits the named signal — the value goes in
payload.value (read by the sum_payload RFM/GMV components) and any other data-cw-*
attribute becomes a payload field:
<button data-cw-signal="order_placed" data-cw-value="49.90">Buy now</button>
<a data-cw-signal="product_viewed" data-cw-product-id="sku_123">Details</a>
<button data-cw-signal="transaction" data-cw-value="120" data-cw-side="buyer">Book</button>Numeric data-cw-* values are coerced to numbers; text values are masked for sensitive
patterns and length-capped. Use canonical keys from ChurnWarn.Metrics. Toggle with
autoCapture.captureDeclaredSignals (default on).
2. E-commerce dataLayer / gtag bridge
If your store already emits GA4 / Google Tag Manager enhanced-ecommerce events, turn on the bridge and they are mirrored to ChurnWarn automatically:
ChurnWarn.configure({
apiKey: '...',
autoCapture: { enabled: true, ecommerce: true }
});| GA4 / GTM event | ChurnWarn signal |
|-----------------|------------------|
| purchase | order_placed (with value) |
| view_item | product_viewed |
| add_to_cart, begin_checkout | cart_created |
| refund | order_returned |
| checkout started but no purchase before unload | cart_abandoned |
Both window.dataLayer.push({ event, ecommerce: { value } }) and
gtag('event', 'purchase', { value }) shapes are supported. Remap or extend the table via
ecommerce: { map: { purchase: 'order_placed', quote_requested: 'transaction_request' } },
and disable the gtag wrapper with ecommerce: { gtag: false }.
Account facts — identify(id, traits) / upsertAccount(id, traits)
Some template signals are slow-changing account facts, not events: the fintech
direct_deposit/kyc_completed flags, the mobile push_opt_in flag, a marketplace account's
role, or a headline money figure. Pass a traits object to identify (or call
upsertAccount) and they are written to PUT /api/accounts/{id}:
ChurnWarn.identify('acct-123', {
planKey: 'pro', // known column
monetaryValue: 480, // known column
valueBasis: 'ltv', // known column
role: 'buyer', // marketplace side
push_opt_in: true, // → attributes bag
kyc_completed: true // → attributes bag
});Known keys (name, email, kind, businessType, monetaryValue, valueBasis,
currency, planKey, lifecycleStage, renewalAt, status, role) map to account
columns; everything else folds into the account attributes bag. The commercial block yields
to a connected billing provider. Put metrics (things you count/sum) in events, facts
(a balance, an opt-in) in traits.
Recommended event-map configuration
After installing the SDK, open Settings → Event map in your ChurnWarn project and add these mappings:
| Raw event type | Mapped metric | Condition (optional) |
|----------------|--------------|----------------------|
| element_clicked | feature_used | — |
| page_viewed | active_user | — |
| page_viewed | feature_used | {"payload.route":"/billing*"} |
| session_ended | session | — |
| session_heartbeat | active_user | — |
For element_clicked and page_viewed you only need one row each — conditions let you fan-out the same raw type to different metrics. Enable the Pattern checkbox on element_clicked if you still have old element_clicked.* data from v0.5.
Constants are available on ChurnWarn.AutoCaptureEvents.
serverUrl
- If you omit
serverUrlor pass an empty string, the SDK useswindow.location.originwhen sending (same-origin as the page). That matches setups where the SPA and the API share a host (for example behind a reverse proxy that serves/api). - If your UI and API live on different origins, set
serverUrlto the gateway root without a trailing slash (for examplehttps://api.example.com). Requests go to{serverUrl}/api/events.
tenantId
- Optional. If omitted, the server resolves the tenant from the JWT
tenant_idclaim. - Pass
tenantIdwhen you need to override the claim (must be a UUID string the authenticated user is allowed to access).
token
- A Bearer access token accepted by your Gateway (same style as the web app). You may pass either
'<token>'or'Bearer <token>'; the SDK normalizes to a singleAuthorization: Bearer …header.
captureEvent(externalAccountId, eventName, extraData?)
| Argument | Description |
|----------|---------------|
| externalAccountId | Customer account id in your system (required, max 100 characters after trim). |
| eventName | Event type string. Use ChurnWarn.Metrics.*, ChurnWarn.RawEvents.*, ChurnWarn.AutoCaptureEvents.*, or any custom string your tenant maps in ChurnWarn (max 100 characters). |
| extraData | Optional plain object. Serialized with JSON.stringify and sent as the API payload field (a JSON string on the wire). Omitted or null becomes {}. |
The HTTP call uses fetch and returns immediately; success and failure are handled internally.
Event name constants
There are no TypeScript-style enums in the bundle. Use frozen objects whose values are the strings sent to the API:
ChurnWarn.Metrics— canonical keys such asLOGIN→'login',FEATURE_USED→'feature_used', etc. (mirror of backendDefaultDashboardMetricKeys).ChurnWarn.RawEvents— dotted aliases such asAPP_LOGIN→'app.login'(mirror of backendEventTestGeneratorAliases).ChurnWarn.AutoCaptureEvents— auto-capture event names such asPAGE_VIEWED→'page_viewed'.
Example:
ChurnWarn.captureEvent('acct-1', ChurnWarn.RawEvents.APP_FEATURE_USED, { feature: 'export_csv' });
ChurnWarn.captureEvent('acct-1', ChurnWarn.Metrics.FEATURE_USED, { feature: 'export_csv' });Batching, retries, and offline delivery
Unlike the server SDKs, the browser SDK has no tunable retry options — the delivery policy is fixed so the bundle stays small and predictable:
| Behaviour | Value |
|-----------|-------|
| Batch flush | when 10 events are queued, or every 2 s, whichever comes first |
| Retried failures | network errors and HTTP 5xx |
| Not retried | 4xx (bad token, validation) — the event is dropped and logged |
| Offline queue | up to 100 events in localStorage; oldest dropped when full |
| Queue drained | on SDK load, and 1.5 s after the browser fires an online event |
A failed batch is written to localStorage under churn_warn_offline_q and retried in a later
page load. Events are redacted before they are persisted, so sensitive payload data never
lands on disk. A drain that itself fails is not re-queued, which prevents a poison batch from
looping forever.
session_ended is sent as a single keepalive request so it survives page unload; if that
fails it also falls back to the offline queue.
Call flush() to send the pending batch immediately instead of waiting for the 2 s timer —
useful just before a deliberate navigation or logout.
Error handling and logging
The SDK is written so callers do not need try/catch:
configure,identify, andcaptureEventcatch their own synchronous errors and log with the prefix[ChurnWarn]viaconsole.warn.- Network and non-OK HTTP responses are logged the same way;
captureEventnever throws and does not reject a promise to the caller (it returnsundefined).
Check the browser console if events do not appear server-side.
CORS and authentication
- The Gateway route is
POST /api/eventsand requiresAuthorization: Bearer <token>. Your page origin must be allowed by the API CORS configuration. - The token must be valid and, when
tenantIdis omitted, include a usabletenant_idclaim unless you passtenantIdexplicitly.
Browser support
The bundle targets ES5 syntax for broad compatibility. You still need:
fetchPromisesessionStorage(for auto-capture sessions)
Very old browsers without these need polyfills. JSON must exist for non-empty extraData objects.
Building the bundle from source
From this directory:
npm install
npm run buildOutput: dist/churn-warn.umd.min.js (IIFE, global name ChurnWarn).
Source entry: src/index.js.
API reference summary
| Export / global | Type | Description |
|-----------------|------|-------------|
| configure(opts) | function | opts: { apiKey?, token?, serverUrl?, tenantId?, source?, redact?, autoCapture? } — redact defaults to true (see Privacy); autoCapture accepts true or { enabled, trackClicks, sessionTimeoutMinutes, captureErrors, captureRageClicks, captureFormAbandons, captureScrollDepth, captureFeatures, captureDeclaredSignals, ecommerce, routes: { normalize } } |
| identify(externalAccountId, traits?) | function | Account id for auto-capture; traits upserts account facts to PUT /api/accounts |
| upsertAccount(externalAccountId, traits) | function | Write account facts without touching the auto-capture session |
| captureEvent(accountId, eventName, extraData?) | function | Queues one event for the next batch flush; always non-throwing |
| flush() | function | Sends the pending batch immediately instead of waiting for the 2 s timer |
| Metrics | object | Canonical eventType strings — all business-type template signals |
| RawEvents | object | Dotted raw eventType aliases the gateway maps to Metrics |
| PayloadFields | object | Payload keys read by sum_payload/avg_payload (value, side, quantity) |
| AccountAttributes | object | Account fact keys (direct_deposit, push_opt_in, …) |
| BusinessTypes | object | Dashboard-template keys (ecommerce, fintech, …) |
| AutoCaptureEvents | object | Read-only catalog of auto-capture eventType strings |
| version | string | SDK version |
