Atribu
Tracking

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

Install
npm install @atribu/tracker

Quick start

src/App.tsx
import { useEffect } from "react";
import { init } from "@atribu/tracker";

function App() {
  useEffect(() => {
    init({ trackingKey: "trk_live_your_key" });
  }, []);

  return <div>Your app</div>;
}
src/components/AtribuProvider.tsx
"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:

src/app/layout.tsx
import { AtribuProvider } from "@/components/AtribuProvider";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AtribuProvider>{children}</AtribuProvider>
      </body>
    </html>
  );
}
src/App.vue
<script setup>
import { onMounted } from "vue";
import { init } from "@atribu/tracker";

onMounted(() => {
  init({ trackingKey: "trk_live_your_key" });
});
</script>
index.html
<script type="module">
  import { init } from "@atribu/tracker";
  init({ trackingKey: "trk_live_your_key" });
</script>

Configuration

All options passed to init():

OptionTypeDefaultDescription
trackingKeystringrequiredYour ingest key from Settings > Tracking
apiHoststringwindow.location.originOrigin for the collect endpoint
trackingEndpointstring{apiHost}/api/tracking/collectFull URL override for tracking
interceptMetaFbqbooleantrueIntercept Meta Pixel fbq() calls for server-side deduplication
metaBridgePageviewbooleanfalseAlso mirror Meta PageView events
sessionTimeoutMinutesnumber30Session inactivity timeout (1-120 min)
sessionModestring"inactivity_only""inactivity_only" or "inactivity_or_source_change"
heartbeatIntervalSecondsnumber | false60Engagement heartbeat interval (crash-recovery checkpoint). 0 or false to disable
enableEngagementbooleanfalseScroll depth, time on page, and a per-minute heartbeat
enableClickQualitybooleanfalseTrack rage clicks and dead clicks
enableWebVitalsbooleanfalseCapture Core Web Vitals (LCP, FCP, CLS, INP, TTFB)
enableCtaTrackingbooleanfalseAuto-detect and track CTA button visibility
enableVideoTrackingbooleanfalseAuto-detect <video> elements and track milestones
enableGtagBridgebooleanfalseShare a transaction id between browser gtag()/dataLayer conversions and Atribu's Google upload
enableTtqBridgebooleanfalseShare an event id between TikTok pixel (ttq.track) events and Atribu's TikTok Events API export
enableSchedulerIdentitybooleanfalseIdentify 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
ignoredPagesstring[]—URL patterns to skip tracking (e.g., ["/admin/*"])
customPropertiesobject | function—Static properties or function merged into every event
transformRequestfunction—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

Enable the gtag/dataLayer transaction id 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_id from an existing gtag('event', 'purchase' | 'conversion', {...}) call or a GTM-style dataLayer.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 Manager transactionId) 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

Enable the TikTok pixel (ttq) event id 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 as CompletePayment and leads as SubmitForm, the same names the server-side event uses by default.
  • Captures the event_id from a ttq.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

Static properties
init({
  trackingKey: "trk_live_...",
  customProperties: {
    environment: "production",
    appVersion: "2.1.0",
  },
});
Dynamic properties (function)
init({
  trackingKey: "trk_live_...",
  customProperties: (ctx) => ({
    currentPath: ctx.path,
    isLoggedIn: !!localStorage.getItem("token"),
  }),
});

Transform request (middleware)

Filter out events
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

Basic event
import { track } from "@atribu/tracker";

track("button_clicked", { buttonName: "pricing_cta" });
Revenue event
import { trackRevenue } from "@atribu/tracker";

trackRevenue("purchase", 99.99, "USD", { plan: "Pro" });
Self-describing event (with schema)
import { trackSelfDescribing } from "@atribu/tracker";

trackSelfDescribing({
  eventSchema: "com.atribu.checkout",
  schemaVersion: 1,
  payload: { step: "payment", method: "credit_card" },
});

Identifying users

Identify after form submission
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.

Set consent after cookie banner
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

Track when an element becomes visible
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

Flush and reset
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:

CategoryEvents
NavigationPage views, SPA navigation (pushState, popstate, hashchange)
SessionsSession start/end, configurable timeout
EngagementScroll depth, time on page, heartbeat
LinksOutbound link clicks, file downloads (PDF, ZIP, etc.)
FormsForm submissions with email/phone extraction
BookingsGHL, Calendly, Cal.com widget completions
Meta Pixelfbq() interception for server-side dedup
Google gtaggtag()/dataLayer transaction id bridge for server-side dedup (optional)
TikTok pixelttq.track event id bridge for Events API dedup (optional)
StripeCheckout completion detection (?session_id=cs_*)
GHL Formsfetch() interception for div-based forms
Click QualityRage clicks (3+ rapid), dead clicks (optional)
Web VitalsLCP, FCP, CLS, INP, TTFB (optional)
CTA VisibilityAction button/link visibility tracking (optional)
Video<video> play/pause/milestones (optional)
ErrorsUncaught JavaScript errors
Bot DetectionFilters bots, tags AI agents (ChatGPT, Claude, etc.)

Script tag vs npm

npm PackageScript Tag
Installnpm install @atribu/tracker<script src="..."></script>
TypeScriptFull type safetyNo types
FrameworksReact, Next.js, Vue, SvelteVanilla HTML only
SSRBuilt-in no-op clientClient-side only
ConfigTyped init() objectWindow variables
ModuleESM + CommonJSIIFE global
FunctionalityIdenticalIdentical

TypeScript support

All types are exported:

Import types
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.

Safe in any environment
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");

On this page