npm Package (@atribu/tracker)
Official TypeScript SDK for Atribu — install via npm for full type safety, framework integration, and SSR support
The @atribu/tracker npm package is the official TypeScript SDK for Atribu. It bundles the full tracker runtime with type-safe configuration, framework-agnostic design, and automatic SSR safety.
Installation
npm install @atribu/trackerQuick start
import { useEffect } from "react";
import { init } from "@atribu/tracker";
function App() {
useEffect(() => {
init({ trackingKey: "trk_live_your_key" });
}, []);
return <div>Your app</div>;
}"use client";
import { useEffect } from "react";
import { init } from "@atribu/tracker";
export function AtribuProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
init({ trackingKey: "trk_live_your_key" });
}, []);
return <>{children}</>;
}Then wrap your root layout:
import { AtribuProvider } from "@/components/AtribuProvider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<AtribuProvider>{children}</AtribuProvider>
</body>
</html>
);
}<script setup>
import { onMounted } from "vue";
import { init } from "@atribu/tracker";
onMounted(() => {
init({ trackingKey: "trk_live_your_key" });
});
</script><script type="module">
import { init } from "@atribu/tracker";
init({ trackingKey: "trk_live_your_key" });
</script>Configuration
All options passed to init():
| Option | Type | Default | Description |
|---|---|---|---|
trackingKey | string | required | Your ingest key from Settings > Tracking |
apiHost | string | window.location.origin | Origin for the collect endpoint |
trackingEndpoint | string | {apiHost}/api/tracking/collect | Full URL override for tracking |
interceptMetaFbq | boolean | true | Intercept Meta Pixel fbq() calls for server-side deduplication |
metaBridgePageview | boolean | false | Also mirror Meta PageView events |
sessionTimeoutMinutes | number | 30 | Session inactivity timeout (1-120 min) |
sessionMode | string | "inactivity_only" | "inactivity_only" or "inactivity_or_source_change" |
heartbeatIntervalSeconds | number | false | 60 | Engagement heartbeat interval (crash-recovery checkpoint). 0 or false to disable |
enableEngagement | boolean | false | Scroll depth, time on page, and a per-minute heartbeat |
enableClickQuality | boolean | false | Track rage clicks and dead clicks |
enableWebVitals | boolean | false | Capture Core Web Vitals (LCP, FCP, CLS, INP, TTFB) |
enableCtaTracking | boolean | false | Auto-detect and track CTA button visibility |
enableVideoTracking | boolean | false | Auto-detect <video> elements and track milestones |
enableGtagBridge | boolean | false | Share a transaction id between browser gtag()/dataLayer conversions and Atribu's Google upload |
enableTtqBridge | boolean | false | Share an event id between TikTok pixel (ttq.track) events and Atribu's TikTok Events API export |
enableSchedulerIdentity | boolean | false | Identify the visitor from a Calendly / GoHighLevel / Acuity thank-you redirect and strip those parameters from the URL. Also needs the profile setting — see Scheduler Redirects |
ignoredPages | string[] | — | URL patterns to skip tracking (e.g., ["/admin/*"]) |
customProperties | object | function | — | Static properties or function merged into every event |
transformRequest | function | — | Middleware to modify or suppress events before sending |
Four of these five are dropped server-side no matter what
enableEngagement, enableClickQuality, enableWebVitals, and
enableVideoTracking all emit the browser event under the name
engagement. Atribu's ingest unconditionally drops every event named
engagement before it is enriched or stored — the dashboard surfaces that
data used to feed were retired. There is no server-side setting that brings
it back; turning on any of these four client flags produces events the
server silently discards. enableCtaTracking is different: it emits
view_content, a normal event type with no such drop, so turning it on
does create real, attribution-eligible events.
Google gtag bridge
init({
trackingKey: "trk_live_...",
enableGtagBridge: true,
});Google only deduplicates a browser gtag() conversion against a
server-uploaded one when both carry the exact same transaction_id. With
enableGtagBridge: true, the tracker:
- Captures the
transaction_idfrom an existinggtag('event', 'purchase' | 'conversion', {...})call or a GTM-styledataLayer.push({ event: 'purchase', ecommerce: { transaction_id } })and reuses it as the id for Atribu's own tracked event, so the server-side Google upload (Data ManagertransactionId) sends the identical value. - Generates and injects a transaction id when the call has none, pushing it back into the outgoing gtag/dataLayer call before it proceeds, so the conversion Google actually receives from the browser carries the same id the server upload will use.
An existing transaction_id is never overwritten. Off by default — most
sites don't run gtag.js at all, and this only ever observes calls a page is
already making.
TikTok pixel bridge
init({
trackingKey: "trk_live_...",
enableTtqBridge: true,
});With the script tag, set window.ATRIBU_ENABLE_TTQ_BRIDGE = true before the
tracker loads.
TikTok deduplicates a TikTok pixel event against an Events API event only when
both carry the same event_id. When a Conversion Sync rule sends conversions
to TikTok, Atribu's server-side event uses Atribu's own event id — this bridge
puts that same id on the browser pixel event. With enableTtqBridge: true, the
tracker:
- Fires the pixel for Atribu's conversions. When Atribu tracks a conversion
(a form submit, a purchase, a booking…) and the page has the TikTok pixel,
it calls
ttq.track(event, { value, currency }, { event_id })with Atribu's event id. Purchases go out asCompletePaymentand leads asSubmitForm, the same names the server-side event uses by default. - Captures the
event_idfrom attq.track(...)call your site already makes and reuses it as the id of Atribu's own event, so the server-side event sends the identical value. - Links or injects an id when your call has none: if Atribu tracked the same conversion a moment earlier (for example from the same click), its id is injected into your call; otherwise a new one is generated, injected, and used for Atribu's event.
An existing event_id is never overwritten, and when your own ttq.track
fires first Atribu does not fire a second pixel event for the same moment. The
bridge also sends TikTok's _ttp cookie so the server-side event can be matched
to the browser. If a rule sets a custom TikTok event name, use that same name in
any ttq.track call you make, or the two events will not pair. Off by default —
it reads from and fires into your TikTok pixel, so it must be turned on
deliberately.
Custom properties
init({
trackingKey: "trk_live_...",
customProperties: {
environment: "production",
appVersion: "2.1.0",
},
});init({
trackingKey: "trk_live_...",
customProperties: (ctx) => ({
currentPath: ctx.path,
isLoggedIn: !!localStorage.getItem("token"),
}),
});Transform request (middleware)
init({
trackingKey: "trk_live_...",
transformRequest: (event) => {
// Suppress events from admin pages
if (event.path?.startsWith("/admin")) return null;
// Add custom header
event.payload.buildId = "abc123";
return event;
},
});Tracking events
import { track } from "@atribu/tracker";
track("button_clicked", { buttonName: "pricing_cta" });import { trackRevenue } from "@atribu/tracker";
trackRevenue("purchase", 99.99, "USD", { plan: "Pro" });import { trackSelfDescribing } from "@atribu/tracker";
trackSelfDescribing({
eventSchema: "com.atribu.checkout",
schemaVersion: 1,
payload: { step: "payment", method: "credit_card" },
});Identifying users
import { identify } from "@atribu/tracker";
identify({
email: "[email protected]",
firstName: "Jane",
lastName: "Smith",
phone: "+1-555-0123",
});Critical for attribution
Without identify(), anonymous visitors cannot be linked to payments or CRM events. Call it on every form submission, login, or signup. See User Identification for details.
Consent management
import { setConsent } from "@atribu/tracker";
// After user accepts analytics cookies but not ads
setConsent({ analytics: true, ads: false });Consent is enforced: ads: false stops _fbc/_fbp, saved click ids, the Meta/gtag bridges and every export to ad platforms; analytics: false keeps identifiers in memory for the page only. Pass consentMode: "required" (and optionally an initial consent) to init() to deny everything until a grant; Google Consent Mode v2 and IAB TCF are read automatically. getConsent() returns the current state. Details: Visitor Consent.
Impressions
import { observeImpression } from "@atribu/tracker";
// CSS selector
const cleanup = observeImpression("#hero-banner", { section: "hero" });
// DOM element
const el = document.querySelector(".pricing-card");
const cleanup2 = observeImpression(el, { plan: "pro" }, { threshold: 0.8 });
// Cleanup on unmount
cleanup();
cleanup2();Lifecycle
import { flush, reset } from "@atribu/tracker";
// Force-send all queued events (before navigation)
flush();
// Clear all stored state (on logout)
reset();Auto-captured events
The package automatically captures these events with zero configuration:
| Category | Events |
|---|---|
| Navigation | Page views, SPA navigation (pushState, popstate, hashchange) |
| Sessions | Session start/end, configurable timeout |
| Engagement | Scroll depth, time on page, heartbeat |
| Links | Outbound link clicks, file downloads (PDF, ZIP, etc.) |
| Forms | Form submissions with email/phone extraction |
| Bookings | GHL, Calendly, Cal.com widget completions |
| Meta Pixel | fbq() interception for server-side dedup |
| Google gtag | gtag()/dataLayer transaction id bridge for server-side dedup (optional) |
| TikTok pixel | ttq.track event id bridge for Events API dedup (optional) |
| Stripe | Checkout completion detection (?session_id=cs_*) |
| GHL Forms | fetch() interception for div-based forms |
| Click Quality | Rage clicks (3+ rapid), dead clicks (optional) |
| Web Vitals | LCP, FCP, CLS, INP, TTFB (optional) |
| CTA Visibility | Action button/link visibility tracking (optional) |
| Video | <video> play/pause/milestones (optional) |
| Errors | Uncaught JavaScript errors |
| Bot Detection | Filters bots, tags AI agents (ChatGPT, Claude, etc.) |
Script tag vs npm
| npm Package | Script Tag | |
|---|---|---|
| Install | npm install @atribu/tracker | <script src="..."></script> |
| TypeScript | Full type safety | No types |
| Frameworks | React, Next.js, Vue, Svelte | Vanilla HTML only |
| SSR | Built-in no-op client | Client-side only |
| Config | Typed init() object | Window variables |
| Module | ESM + CommonJS | IIFE global |
| Functionality | Identical | Identical |
TypeScript support
All types are exported:
import type {
AtribuConfig,
AtribuClient,
TrackOptions,
IdentifyInput,
ConsentPayload,
TrackingEvent,
} from "@atribu/tracker";SSR safety
When window is undefined (Node.js, Next.js server components, SSR), init() returns a silent no-op client. All methods (track, identify, etc.) are safe to call — they simply do nothing server-side and activate once the code runs in the browser.
import { init, track } from "@atribu/tracker";
// This works in SSR — returns no-op client, no errors
const client = init({ trackingKey: "trk_live_..." });
// This is safe server-side — silently ignored
track("page_loaded");