npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mytart

v0.9.9

Published

Multi-Yield Tracking & Analytics Relay Tool — framework-agnostic analytics for any project

Readme

mytart

Multi-Yield Tracking & Analytics Relay Tool — framework-agnostic analytics for any ESM JavaScript/TypeScript project.

Table of Contents

mytart makes direct HTTP calls to analytics provider endpoints via axios — no bloated SDK wrappers, no global state, no lock-in.

Features

  • 🔌 17 providers out of the box: Google Analytics 4, Google Ads, Mixpanel, Segment, Amplitude, Plausible, PostHog, Meta Pixel, Microsoft Clarity, Hotjar, Heap, TikTok, Snapchat, Twitter/X, Reddit, Pinterest, Microsoft Ads
  • 🏷️ Standardized event taxonomy: 36 standard events (purchase, lead, sign_up, bet_placed, first_bet, deposit, etc.) auto-mapped to each provider's native conventions
  • 🎯 Provider groups: route events to subsets of providers (marketing, product, infrastructure, physical) — send conversion events only to ad platforms, product events only to analytics tools
  • 🌐 Universal: works in Node.js, browsers, and any JS framework (Next.js, Remix, Astro, SvelteKit, etc.)
  • 🔷 TypeScript-first: precise typings, object-parameter style for great DX
  • 📦 Dual ESM/CJS output: works with import and require
  • 🪶 Lightweight: direct HTTP via axios, no SDK overhead
  • 🤖 Bot filtering: optionally ignore bots and crawlers via ua-parser-js
  • 🔗 Cross-provider linking: auto-captures click IDs (gclid, fbclid, msclkid, etc.) and cookies, then injects them into every provider call for unified attribution
  • 🪪 Browser fingerprint: uses ThumbmarkJS to generate a stable, cookieless device identifier as anonymousId — consistent across sessions, cache clears, and incognito mode. Enabled by default, lazy-loaded, SSR-safe
  • 🔄 Automatic retries: configurable exponential backoff with jitter for 429/5xx/network errors — transparent to all providers via axios interceptor
  • 📬 Dead-letter queue: failed retryable events are queued in memory for later replay, with optional callback for external persistence
  • 🔍 Debug mode: global debug flag activates provider-native validation endpoints (GA4, Google Ads, Snapchat) and captures full request/response details in TrackResult.debugInfo
  • 🛡️ Global consent flag: consent: true/false on MytartConfig auto-configures consent across all supporting providers (GA Consent Mode v2 + Meta Pixel grant/revoke)
  • ✅ Node.js ≥ 18

Installation

npm install mytart
# or
pnpm add mytart
# or
yarn add mytart

Quick Start

import { Mytart } from 'mytart';

const analytics = new Mytart({
  providers: [
    { provider: 'segment', writeKey: 'YOUR_WRITE_KEY' },
    { provider: 'posthog', apiKey: 'phc_YOUR_KEY' },
  ],
  defaultUserId: 'user_123',
  // browserFingerprint is enabled by default — a stable device fingerprint
  // is automatically generated and used as anonymousId for all providers
});

// Track an event
await analytics.track({ event: 'signup', properties: { plan: 'pro' } });

// Identify a user
await analytics.identify({ userId: 'user_123', traits: { name: 'Alice', email: '[email protected]' } });

// Track a page view
await analytics.page({ url: 'https://example.com/pricing', name: 'Pricing' });

Documentation

For detailed documentation on specific topics, see the docs/ folder:

Guides

  • Betting / iGaming Events — Complete guide to betting event properties, provider mappings, and the property mapping system

Architecture

  • Mytart Core — Main class, parallel dispatch, provider groups, typed track overloads
  • Base Provider — Abstract class, executeRequest pattern, createHttp factory
  • Event Taxonomy — Standardized event name mapping across providers
  • Cross-Provider Linking — Click ID + cookie capture, xpl_ properties
  • HTTP Client — Axios factory, retry interceptor, DLQ, debug capture
  • PII Hashing — SHA-256 utility, hashUserData, provider-specific wrappers
  • State Management — Central userId/anonymousId/sessionId, trait persistence
  • Consent Mode — Global toggle, GA Consent Mode v2, Meta binary, Clarity cookie
  • Browser Fingerprinting — ThumbmarkJS, lazy resolution, cookieless device ID
  • Bot Filtering — ua-parser-js bot detection, global toggle, fail-open

Provider Documentation

  • Google Analytics — GA4 Measurement Protocol, gtag.js, Consent Mode v2
  • Google Ads — Conversion tracking, Enhanced Conversions API
  • Meta Pixel — fbq pixel, Conversions API, Advanced Matching
  • TikTok — ttq pixel, Events API v1.3
  • Snapchat — snaptr pixel, Conversions API v3
  • Twitter/X — twq pixel, Conversions API
  • Reddit — rdt pixel, Conversions API v2.0
  • Pinterest — pintrk tag, Conversions API v5
  • Microsoft Ads — UET tag, Offline Conversions API
  • Mixpanel — HTTP API, Title Case events
  • Segment — HTTP API, pass-through event names
  • Amplitude — HTTP API v2, Title Case events
  • Plausible — Events API, privacy-first
  • PostHog — Capture API, $session_id in properties
  • Clarity — Microsoft session replay, browser-only

Providers

Testing Status

| Provider | Status | |---|---| | Google Analytics 4 | Tested and confirmed working | | Google Ads | Not yet tested | | Mixpanel | Not yet tested | | Segment | Not yet tested | | Amplitude | Not yet tested | | Plausible | Not yet tested | | PostHog | Not yet tested | | Meta Pixel | Not yet tested | | Microsoft Clarity | Tested and confirmed working | | Hotjar | Not yet tested | | Heap | Not yet tested | | TikTok | Not yet tested | | Snapchat | Not yet tested | | Twitter/X | Not yet tested | | Reddit | Not yet tested | | Pinterest | Not yet tested | | Microsoft Ads | Not yet tested |

Google Analytics 4

GA4 supports two modes via the appType option:

Server mode (default)

Uses the GA4 Measurement Protocol — direct HTTP calls, no browser APIs. Use this for Node.js, API routes, serverless functions, etc.

{
  provider: 'google-analytics',
  measurementId: 'G-XXXXXXXXXX',
  apiSecret: 'YOUR_SECRET',
  enabled: true,
  // appType defaults to 'server'
}

Browser mode

Injects Google's official gtag.js snippet into the page. Use this for client-side tracking in any framework (React, Vue, Svelte, plain HTML, etc.). No apiSecret needed.

{
  provider: 'google-analytics',
  measurementId: 'G-XXXXXXXXXX',
  appType: 'browser',
  enabled: true,
}

When appType: 'browser' is set:

  • The gtag.js script is loaded once on the first track(), identify(), or page() call
  • All calls use the standard gtag() API — compatible with Google Tag Tester and Tag Assistant
  • SSR-safe: silently succeeds when window is undefined (e.g. during server-side rendering)
  • apiSecret is not required (and not used)

Google Signals (demographics)

To enable demographic data (age, gender, interests) in GA4 reports, set signals: true:

{
  provider: 'google-analytics',
  measurementId: 'G-XXXXXXXXXX',
  appType: 'browser',
  enabled: true,
  signals: true,
}

This does two things automatically:

  1. Passes allow_google_signals: true and allow_ad_personalization_signals: true to gtag('config')
  2. Sets Consent Mode v2 defaults granting ad_personalization, ad_user_data, ad_storage, and analytics_storage

Set signals: false to explicitly disable Google Signals. Omit the flag entirely to use Google's default behaviour.

Note: You must also enable Google Signals in the GA4 admin panel (Admin > Data Settings > Data Collection) for demographic data to appear.

Consent Mode v2

For GDPR/privacy compliance you can control Consent Mode v2 directly. Use defaultConsent to set the initial consent state (emitted before gtag('config')), and updateConsent() to change it at runtime when the user interacts with a cookie banner.

const analytics = new Mytart({
  providers: [{
    provider: 'google-analytics',
    measurementId: 'G-XXXXXXXXXX',
    appType: 'browser',
    enabled: true,
    signals: true,
    defaultConsent: {
      ad_storage: 'denied',
      analytics_storage: 'denied',
      ad_user_data: 'denied',
      ad_personalization: 'denied',
    },
    consentWaitForUpdate: 500, // wait 500ms for consent banner
  }],
});

// After the user accepts the cookie banner:
await analytics.updateConsent({
  ad_storage: 'granted',
  analytics_storage: 'granted',
  ad_user_data: 'granted',
  ad_personalization: 'granted',
});

When both signals: true and defaultConsent are set, the explicit defaultConsent takes precedence over the auto-consent that signals would generate. This lets you combine signals: true (for the config flags) with a GDPR-safe denied-by-default consent flow.

Consent Mode is a no-op in server mode (the Measurement Protocol does not support it).

Google Ads

Google Ads supports two modes via the appType option:

Browser mode (default)

Uses gtag.js to fire conversion events via gtag('event', 'conversion', { send_to: ... }). If GA4 is also configured, the same gtag.js instance is reused.

{
  provider: 'google-ads',
  conversionActionId: 'AW-123456789/AbC1dEfG2hIjK3lM',
  appType: 'browser',
  enabled: true,
}

Server mode (Enhanced Conversions)

Uses the Google Ads API to upload conversion adjustments with hashed user identifiers for enhanced matching.

{
  provider: 'google-ads',
  conversionActionId: 'AW-123456789/AbC1dEfG2hIjK3lM',
  customerId: '123-456-7890',
  accessToken: 'YOUR_OAUTH_TOKEN',
  developerToken: 'YOUR_DEV_TOKEN',
  appType: 'server',
  userIdentifiers: ['email', 'phone'],
  enabled: true,
}

Enhanced Conversions

Configure which user identifiers to send for conversion matching:

{
  provider: 'google-ads',
  conversionActionId: 'AW-123456789/AbC1dEfG2hIjK3lM',
  customerId: '123-456-7890',
  accessToken: 'YOUR_OAUTH_TOKEN',
  developerToken: 'YOUR_DEV_TOKEN',
  appType: 'server',
  userIdentifiers: ['email', 'phone', 'address'],
  defaultUserIdentifier: {
    email: '[email protected]',
    phone: '+15551234567',
    address: {
      firstName: 'Jane',
      lastName: 'Doe',
      countryCode: 'US',
      postalCode: '10001',
    },
  },
  enabled: true,
}

PII fields are automatically normalized and SHA-256 hashed before sending:

  • Email: lowercased, Gmail dots/plus suffix removed, then hashed
  • Phone: normalized to E.164 format (+CCNNNNNNNN), then hashed
  • Address: first name, last name, street address lowercased and hashed; country code, postal code, city, state are NOT hashed

Conversion tracking with value

Pass orderId, value, and currency for conversion value reporting and deduplication:

await analytics.track({
  event: 'purchase',
  properties: { items: ['SKU-001'] },
  orderId: 'order-abc-123',
  value: 149.99,
  currency: 'USD',
});

Identify for enhanced conversions

Call identify() to cache user identifiers for subsequent conversions:

await analytics.identify({
  userId: 'user-42',
  traits: {
    email: '[email protected]',
    phone: '+15551234567',
  },
});

// Subsequent track() calls will include the cached identifiers
await analytics.track({ event: 'purchase', value: 99.99, currency: 'USD' });

Event taxonomy

Google Ads uses the standardized event taxonomy by default. You can customize mappings or disable auto-mapping:

{
  provider: 'google-ads',
  conversionActionId: 'AW-123456789/AbC1dEfG2hIjK3lM',
  appType: 'browser',
  eventTaxonomy: {
    customMappings: {
      purchase: 'online_sale',
      lead: 'qualified_lead',
    },
  },
  enabled: true,
}

Mixpanel

{ provider: 'mixpanel', token: 'YOUR_TOKEN', apiUrl?: string }

Segment

{ provider: 'segment', writeKey: 'YOUR_WRITE_KEY', apiUrl?: string }

Amplitude

{ provider: 'amplitude', apiKey: 'YOUR_API_KEY', apiUrl?: string }

Plausible

{ provider: 'plausible', domain: 'example.com', apiUrl?: string, userAgent?: string, xForwardedFor?: string }

Note: Plausible does not support identify() — it returns an error result.

PostHog

{ provider: 'posthog', apiKey: 'phc_YOUR_KEY', apiUrl?: string }

Meta Pixel

Meta Pixel supports two modes via the appType option:

Server mode (default)

Uses the Meta Conversions API — direct HTTP calls, no browser APIs. Use this for Node.js, API routes, serverless functions, etc.

{
  provider: 'meta-pixel',
  pixelId: '123456789',
  accessToken: 'YOUR_ACCESS_TOKEN',
  enabled: true,
  // appType defaults to 'server'
}

PII fields in user_data (em, ph, fn, ln, ge, db, ct, st, zp, country) are automatically SHA-256 hashed before being sent to the Conversions API. Already-hashed values are not double-hashed.

Browser mode

Injects Meta's official fbevents.js snippet into the page. Use this for client-side tracking in any framework.

{
  provider: 'meta-pixel',
  pixelId: '123456789',
  appType: 'browser',
  advancedMatching: { em: '[email protected]' },
  enabled: true,
}

When appType: 'browser' is set:

  • The fbevents.js script is loaded once on the first track(), identify(), or page() call
  • Standard Meta events (e.g. Purchase, AddToCart, ViewContent) use fbq('track', ...) — custom events use fbq('trackCustom', ...)
  • SSR-safe: silently succeeds when window is undefined
  • accessToken is not required

Event deduplication

Pass eventId via context to deduplicate browser + server events:

await analytics.track({
  event: 'Purchase',
  properties: { currency: 'USD', value: 42 },
  context: { eventId: 'order-abc-123' },
});

In browser mode this passes { eventID: 'order-abc-123' } as the 4th fbq() parameter. In server mode it sets the event_id field in the Conversions API payload.

Identify

In browser mode, identify() re-calls fbq('init', pixelId, newTraits) to update Advanced Matching data. In server mode, traits are cached in memory and included as user_data in all subsequent track() / page() calls.

Consent

Meta Pixel uses a simple binary consent model (fbq('consent', 'grant') / fbq('consent', 'revoke')).

The easiest way to manage Meta consent is via the global consent flag on MytartConfig:

const analytics = new Mytart({
  consent: true,   // auto-grants consent for all supporting providers (GA + Meta)
  providers: [
    { provider: 'meta-pixel', pixelId: '123456', appType: 'browser', enabled: true },
    { provider: 'google-analytics', measurementId: 'G-XXX', appType: 'browser', enabled: true },
  ],
});

You can also manage consent at runtime via the standard updateConsent() API — ad_storage maps to Meta's binary model:

// Grant consent (bridges ad_storage: 'granted' → fbq('consent', 'grant'))
await analytics.updateConsent({ ad_storage: 'granted' });

// Revoke consent (bridges ad_storage: 'denied' → fbq('consent', 'revoke'))
await analytics.updateConsent({ ad_storage: 'denied' });

For direct low-level access, use the provider instance:

import { MetaPixelProvider } from 'mytart';

const provider = new MetaPixelProvider({ provider: 'meta-pixel', pixelId: '123', appType: 'browser' });
await provider.updatePixelConsent(true);   // fbq('consent', 'grant')
await provider.updatePixelConsent(false);  // fbq('consent', 'revoke')

Microsoft Clarity

Microsoft Clarity is a free behavioral analytics tool that provides session recordings, heatmaps, and insights. Clarity is browser-only — there is no server mode.

{
  provider: 'clarity',
  projectId: 'YOUR_PROJECT_ID',
  enabled: true,
}

When enabled:

  • The official https://www.clarity.ms/tag/{projectId} script is loaded once on the first track(), identify(), or page() call
  • track() fires clarity('event', eventName) and sets each property as a custom tag via clarity('set', key, value)
  • identify() calls clarity('identify', userId, sessionId, undefined, friendlyName) with session ID as the native custom-session-id parameter, and sets remaining traits as custom tags
  • page() fires a PageView event and sets pageUrl, pageName, and referrer as custom tags
  • SSR-safe: silently succeeds when window is undefined

Cookie consent

By default Clarity operates in cookieless mode. To enable cookie-based tracking, set cookie: true:

{
  provider: 'clarity',
  projectId: 'YOUR_PROJECT_ID',
  cookie: true,
  enabled: true,
}

This calls clarity('consent') during initialisation so Clarity can set cookies for more accurate session tracking.

Hotjar

Hotjar provides heatmaps, session recordings, and user feedback. Hotjar is browser-only — there is no server mode.

{
  provider: 'hotjar',
  siteId: 123456,
  enabled: true,
}

When enabled:

  • The official Hotjar script is loaded once on the first track(), identify(), or page() call
  • track() fires hj('event', eventName) and sets properties via hj('tagRecording', ...)
  • identify() calls hj('identify', userId, traits) to attach user information
  • page() calls hj('stateChange', url) for SPA navigation tracking
  • SSR-safe: silently succeeds when window is undefined

Configuration

  • siteId — Your Hotjar site ID (required)
  • version — Hotjar SDK version, defaults to 6
  • debug — Enable debug mode

Heap

Heap provides product analytics with retroactive event capture. Heap supports both browser and server modes.

// Browser mode
{
  provider: 'heap',
  appId: '123456789',
  appType: 'browser',
  enabled: true,
}

// Server mode (HTTP API)
{
  provider: 'heap',
  appId: '123456789',
  appType: 'server',
  enabled: true,
}

Browser mode

In browser mode, Heap uses the official heap.js SDK via window.heap() calls:

  • track() calls heap('track', eventName, properties)
  • identify() calls heap('identify', userId) and heap('addUserProperties', traits)
  • page() calls heap('trackPageview', pageData)

Server mode

In server mode, Heap sends events via the HTTP API (https://heapanalytics.com/api):

  • Authentication uses app_id in the request body (no API key required)
  • You must provide either userId or anonymousId for each event
  • Heap's server API does not support batch tracking

Note: Heap's server-side API returns HTTP 200 for all requests, including malformed payloads. Ensure your app_id and identity values are valid before treating a response as success.

TikTok

TikTok supports two modes via the appType option:

Server mode (default)

Uses the TikTok Events API v1.3 — direct HTTP calls, no browser APIs. Use this for Node.js, API routes, serverless functions, etc.

{
  provider: 'tiktok',
  pixelCode: 'CXXXXXXXXXXXXXXXXX',
  accessToken: 'YOUR_ACCESS_TOKEN',
  enabled: true,
  // appType defaults to 'server'
}

PII fields (email, phone_number, external_id) are automatically SHA-256 hashed before being sent to the Events API. ip and user_agent are NOT hashed. Already-hashed values (64-char hex) are not double-hashed.

Browser mode

Injects TikTok's official Pixel script into the page. Use this for client-side tracking in any framework.

{
  provider: 'tiktok',
  pixelCode: 'CXXXXXXXXXXXXXXXXX',
  appType: 'browser',
  enabled: true,
}

When appType: 'browser' is set:

  • The analytics.tiktok.com/i18n/pixel/events.js script is loaded once on the first call
  • track() calls ttq.track(event, properties), identify() calls ttq.identify({ external_id, email, phone_number })
  • page() calls ttq.page()
  • SSR-safe: silently succeeds when window is undefined

Event deduplication

Pass eventId via context to deduplicate browser + server events:

await analytics.track({
  event: 'purchase',
  properties: { value: 42, currency: 'USD' },
  context: { eventId: 'order-abc-123' },
});

Identify

In browser mode, identify() calls ttq.identify({ external_id, email, phone_number }). In server mode, traits are cached in memory and included in all subsequent track() / page() calls — no standalone identify HTTP call.

Conversion enrichment

await analytics.track({
  event: 'purchase',
  properties: { contents: [{ content_id: 'SKU-001', quantity: 1 }] },
  orderId: 'order-abc',
  value: 99.99,
  currency: 'USD',
});

Test events

Use testEventCode on the config to test without affecting production data:

{
  provider: 'tiktok',
  pixelCode: 'CXXXXXXXXXXXXXXXXX',
  accessToken: 'YOUR_TOKEN',
  testEventCode: 'TEST12345',
  enabled: true,
}

Snapchat

Snapchat supports two modes via the appType option:

Server mode (default)

Uses the Snapchat Conversions API v3 — direct HTTP calls, no browser APIs.

{
  provider: 'snapchat',
  pixelId: '1234567890',
  accessToken: 'YOUR_ACCESS_TOKEN',
  enabled: true,
  // appType defaults to 'server'
}

PII fields (em, ph, fn, ln, ge, ct, st, zp, country) are automatically SHA-256 hashed via the shared hashUserData() utility. external_id, sc_click_id, sc_cookie1, client_ip_address, and client_user_agent are NOT hashed.

Browser mode

Injects Snapchat's official Snap Pixel into the page.

{
  provider: 'snapchat',
  pixelId: '1234567890',
  appType: 'browser',
  enabled: true,
}

When appType: 'browser' is set:

  • The sc-static.net/scevent.min.js script is loaded once on the first call
  • track() calls snaptr('track', event, params), page() calls snaptr('track', 'PAGE_VIEW')
  • identify() re-calls snaptr('init', pixelId, userData) for advanced matching
  • SSR-safe: silently succeeds when window is undefined

Event deduplication

Pass eventId via context — maps to client_dedup_id in browser mode, event_id in server mode.

Identify

In browser mode, identify() re-calls snaptr('init', pixelId, { user_email, user_phone_number }) to update advanced matching data. In server mode, traits are cached in memory and included in all subsequent track() / page() calls.

Debug / validation mode

Set debug: true on the Snapchat config (or global debug: true) to route server-mode requests to the validation endpoint (/events/validate) instead of the live endpoint:

{
  provider: 'snapchat',
  pixelId: '1234567890',
  accessToken: 'YOUR_TOKEN',
  debug: true,
  enabled: true,
}

Test events

Use testEventCode on the config to test without affecting production data.

Twitter/X

Twitter/X supports two modes via the appType option:

Server mode (default)

Uses the X Conversions API — direct HTTP calls with OAuth 1.0a authentication (HMAC-SHA1 signatures, no external dependency).

{
  provider: 'twitter',
  pixelId: 'oka17',
  appType: 'server',
  oauthCredentials: {
    consumerKey: 'YOUR_CONSUMER_KEY',
    consumerSecret: 'YOUR_CONSUMER_SECRET',
    accessToken: 'YOUR_ACCESS_TOKEN',
    accessTokenSecret: 'YOUR_ACCESS_TOKEN_SECRET',
  },
  enabled: true,
}

PII fields are SHA-256 hashed: email → hashed_email, phone → hashed_phone_number. twclid, ip_address, and user_agent are NOT hashed. Identifiers are sent as an array of objects (each identifier is its own object).

Browser mode

Injects X's official Pixel script into the page.

{
  provider: 'twitter',
  pixelId: 'oka17',
  appType: 'browser',
  enabled: true,
}

When appType: 'browser' is set:

  • The static.ads-twitter.com/uwt.js script is loaded once on the first call
  • track() calls twq('event', eventTag, params), page() calls twq('event', 'PageView')
  • identify() passes PII (email, phone) via event params — the browser pixel auto-hashes PII
  • SSR-safe: silently succeeds when window is undefined

Identify

In browser mode, identify() sends a PageView event with PII params for advanced matching. In server mode, traits are cached in memory and included in all subsequent track() / page() calls — no standalone identify HTTP call.

Conversion enrichment

value (sent as string), currency (mapped to price_currency), orderId (mapped to conversion_id) are forwarded in the conversion payload. Conversion time uses ISO 8601 format.

Reddit

Reddit supports two modes via the appType option:

Server mode (default)

Uses the Reddit Conversions API v2.0 — direct HTTP calls, no browser APIs.

{
  provider: 'reddit',
  pixelId: 't2_abc123',
  accountId: 't2_abc123',
  accessToken: 'YOUR_ACCESS_TOKEN',
  enabled: true,
  // appType defaults to 'server'
}

email and external_id are SHA-256 hashed before sending. ip_address, user_agent, and uuid (Reddit _rdt_uuid cookie) are NOT hashed.

Browser mode

Injects Reddit's official Pixel script into the page.

{
  provider: 'reddit',
  pixelId: 't2_abc123',
  appType: 'browser',
  enabled: true,
}

When appType: 'browser' is set:

  • The redditstatic.com/ads/pixel.js script is loaded once on the first call
  • track() calls rdt('track', event, params), page() calls rdt('track', 'PageVisit')
  • identify() re-calls rdt('init', pixelId, { externalId, email }) for advanced matching
  • SSR-safe: silently succeeds when window is undefined

Standard events

Reddit only supports 8 standard event types natively: PageVisit, ViewContent, Search, AddToCart, AddToWishlist, Purchase, Lead, SignUp. All other events are sent as tracking_type: 'Custom' with a custom_event_name field.

Identify

In browser mode, identify() re-calls rdt('init', pixelId, { externalId, email }). In server mode, traits are cached and included in subsequent calls.

Conversion enrichment

value (mapped to value_decimal), currency, orderId (mapped to order_id), itemCount/item_count, and contents (mapped to products) are forwarded in the event_metadata object.

Test mode

Set testMode: true on the config — events are processed but not used for ad optimization:

{
  provider: 'reddit',
  pixelId: 't2_abc123',
  accountId: 't2_abc123',
  accessToken: 'YOUR_TOKEN',
  testMode: true,
  enabled: true,
}

Pinterest

Pinterest supports two modes via the appType option:

Server mode (default)

Uses the Pinterest Conversions API v5 — direct HTTP calls, no browser APIs.

{
  provider: 'pinterest',
  tagId: '123456789012',
  adAccountId: '123456789012',
  accessToken: 'YOUR_ACCESS_TOKEN',
  enabled: true,
  // appType defaults to 'server'
}

PII fields (em, ph, fn, ln, ge, db, ct, st, zp, country) are SHA-256 hashed via hashUserData(), then wrapped in arrays per Pinterest's specification (e.g. em: ['hash1']). external_id is also hashed and wrapped in an array. client_ip_address, client_user_agent, and click_id are NOT hashed.

Browser mode

Injects Pinterest's official Tag script into the page.

{
  provider: 'pinterest',
  tagId: '123456789012',
  appType: 'browser',
  enabled: true,
}

When appType: 'browser' is set:

  • The s.pinimg.com/ct/core.js script is loaded once on the first call
  • Browser pixel uses concatenated-lowercase event names (e.g. addtocart, viewcontent) — the provider auto-converts from CAPI snake_case
  • identify() calls pintrk('set', { external_id, em }) for enhanced matching
  • page() calls pintrk('track', 'pagevisit')
  • SSR-safe: silently succeeds when window is undefined

Identify

In browser mode, identify() calls pintrk('set', { external_id, em }). In server mode, PII traits are normalized (lowercase, trim) before caching for subsequent calls.

Conversion enrichment

value (sent as string — Pinterest requirement), currency, orderId (mapped to order_id), contents, and num_items are forwarded in the custom_data object. Conversion time uses Unix timestamp in seconds.

Test mode

Set testMode: true on the config — appends ?test=true to the CAPI endpoint:

{
  provider: 'pinterest',
  tagId: '123456789012',
  adAccountId: '123456789012',
  accessToken: 'YOUR_TOKEN',
  testMode: true,
  enabled: true,
}

Microsoft Ads

Microsoft Advertising (Bing Ads) supports two modes via the appType option:

Server mode (default)

Uses the Microsoft Advertising Offline Conversions API — uploads conversion data for offline/server-side matching.

{
  provider: 'microsoft-ads',
  tagId: '12345678',
  accessToken: 'YOUR_OAUTH_TOKEN',
  developerToken: 'YOUR_DEV_TOKEN',
  customerId: '123456',
  accountId: '654321',
  enabled: true,
  // appType defaults to 'server'
}

Server mode requires 4 auth headers: Authorization (Bearer token), DeveloperToken, CustomerAccountId, CustomerId.

PII fields are SHA-256 hashed: email → HashedEmailAddress (lowercased, trimmed), phone → HashedPhoneNumber (trimmed). MicrosoftClickId (msclkid) is NOT hashed — it's the primary attribution key.

Browser mode

Injects the official UET tag script into the page.

{
  provider: 'microsoft-ads',
  tagId: '12345678',
  appType: 'browser',
  enabled: true,
}

When appType: 'browser' is set:

  • The bat.bing.com/bat.js script is loaded once on the first call
  • track() pushes uetq.push('event', eventName, params) with revenue/currency
  • identify() pushes uetq.push('set', { pid: { em: hashedEmail, ph: hashedPhone } }) for Enhanced Conversions
  • page() pushes uetq.push('event', 'page_view', { page_path, page_title })
  • SSR-safe: silently succeeds when window is undefined

defaultConversionName

If you have a single conversion goal in Microsoft Ads, set defaultConversionName to use it for all events:

{
  provider: 'microsoft-ads',
  tagId: '12345678',
  accessToken: 'YOUR_TOKEN',
  developerToken: 'YOUR_DEV_TOKEN',
  customerId: '123456',
  accountId: '654321',
  defaultConversionName: 'Online Purchase',
  enabled: true,
}

Without defaultConversionName, the (mapped) event name is used as the ConversionName.

Enhanced Conversions

Call identify() to cache PII for subsequent server-mode conversions:

await analytics.identify({
  userId: 'user-42',
  traits: {
    email: '[email protected]',
    phone: '+15551234567',
  },
});

// Subsequent track() calls include HashedEmailAddress and HashedPhoneNumber
await analytics.track({ event: 'purchase', value: 99.99, currency: 'USD' });

In browser mode, identify() hashes PII and pushes Enhanced Conversion matching data via uetq.push('set', { pid: { em, ph } }).

msclkid attribution

msclkid from cross-provider linking (xpl_msclkid) maps to MicrosoftClickId in the Offline Conversion payload — the primary key for matching offline conversions to ad clicks:

const analytics = new Mytart({
  crossProviderLinking: true,
  providers: [
    { provider: 'microsoft-ads', tagId: '12345678', accessToken: 'YOUR_TOKEN', /* ... */ enabled: true },
  ],
});

// xpl_msclkid is auto-captured from the URL and included in all track() calls
await analytics.track({ event: 'purchase', value: 99.99, currency: 'USD' });

Page tracking

  • Browser mode: page() pushes uetq.push('event', 'page_view', { page_path, page_title }).
  • Server mode: page() returns success: false with MICROSOFT_ADS_PAGE_NOT_SUPPORTED error — the Offline Conversions API does not support page view tracking.

Conversion enrichment

value → ConversionValue, currency → ConversionCurrencyCode. orderId maps to event_label in browser mode for deduplication. Conversion time uses ISO 8601 format.

Standardized Event Taxonomy

mytart includes a standardized set of 36 event names that automatically map to each provider's native event conventions. When you use a standard event name in track(), it's automatically translated to the correct format for each provider.

Standard events

purchase, lead, sign_up, login, search, add_to_cart, add_to_wishlist, begin_checkout, remove_from_cart, select_item, select_promotion, view_item, view_item_list, view_promotion, view_cart, add_payment_info, add_shipping_info, purchase_refund, subscribe, unsubscribe, contact, generate_lead, schedule, start_trial, complete_registration, donate, share, view_search_results, bet_placed, bet_settled, bet_cancelled, inplay_update, deposit, withdrawal, session_timeout, responsible_gambling_alert, first_bet

How mapping works

When you call analytics.track({ event: 'purchase' }), each provider receives the event in its native format:

| Standard Event | GA4 | Google Ads | Meta Pixel | Mixpanel | Segment / PostHog | |---|---|---|---|---|---| | purchase | purchase | purchase | Purchase | Purchase | purchase | | lead | generate_lead | lead | Lead | Lead | lead | | add_to_cart | add_to_cart | add_to_cart | AddToCart | Add to Cart | add_to_cart | | begin_checkout | begin_checkout | begin_checkout | InitiateCheckout | Begin Checkout | begin_checkout | | sign_up | sign_up | sign_up | CompleteRegistration | Sign Up | sign_up | | purchase_refund | refund | purchase_refund | Purchase | Purchase Refund | purchase_refund |

Non-standard event names (e.g. my_custom_event) are always passed through unchanged to all providers.

Additional provider-specific mappings:

  • TikTok: purchase → CompletePayment, lead → SubmitForm, sign_up → CompleteRegistration, add_to_cart → AddToCart, begin_checkout → InitiateCheckout, view_item → ViewContent
  • Snapchat: purchase → PURCHASE, lead → SIGN_UP, add_to_cart → ADD_CART, view_item → VIEW_CONTENT, begin_checkout → START_CHECKOUT
  • Twitter/X: purchase → Purchase, lead → Lead, sign_up → SignUp, add_to_cart → AddToCart, begin_checkout → CheckoutInitiated, view_item → ContentView
  • Reddit: purchase → Purchase, lead → Lead, sign_up → SignUp, add_to_cart → AddToCart. Only 8 standard events supported; all others sent as 'Custom'
  • Pinterest: purchase → checkout, lead → lead, sign_up → signup, add_to_cart → add_to_cart, view_item → view_content. Browser mode auto-converts to concatenated-lowercase (addtocart, viewcontent)
  • Microsoft Ads: snake_case pass-through similar to GA4/Google Ads. Aliases: generate_lead → lead, complete_registration → sign_up

TrackOptions enrichment

TrackOptions supports additional fields for conversion-focused providers:

await analytics.track({
  event: 'purchase',                    // standard event name
  properties: { items: ['SKU-001'] },
  orderId: 'order-abc-123',            // transaction ID for deduplication
  value: 149.99,                        // conversion value
  currency: 'USD',                      // ISO 4217 currency code
});

These fields are used by conversion-focused providers including Google Ads, Meta Pixel, TikTok, Snapchat, Twitter/X, Reddit, Pinterest, and Microsoft Ads.

Betting / iGaming Events

mytart includes 9 standard betting events designed for sportsbook and iGaming platforms. These events follow the same auto-mapping logic as all other standard events and have fully typed property interfaces.

| Standard Event | GA4 | Google Ads | Meta Pixel | Mixpanel / Amplitude | Segment / PostHog / Plausible | |---|---|---|---|---|---| | bet_placed | purchase | purchase | Purchase | Bet Placed | bet_placed | | bet_settled | bet_settled | (custom) | BetSettled | Bet Settled | bet_settled | | bet_cancelled | refund | (custom) | BetCancelled | Bet Cancelled | bet_cancelled | | inplay_update | inplay_update | (custom) | InplayUpdate | Inplay Update | inplay_update | | deposit | deposit | (custom) | Deposit | Deposit | deposit | | withdrawal | withdrawal | (custom) | Withdrawal | Withdrawal | withdrawal | | session_timeout | session_timeout | (custom) | SessionTimeout | Session Timeout | session_timeout | | responsible_gambling_alert | responsible_gambling_alert | (custom) | ResponsibleGamblingAlert | Responsible Gambling Alert | responsible_gambling_alert | | first_bet | purchase | purchase | Purchase | First Bet | first_bet |

Note: bet_placed and first_bet map to purchase in GA4 and Google Ads so bets appear in native revenue reports. bet_cancelled maps to refund in GA4. Google Ads only has a default mapping for bet_placed and first_bet — use customMappings to map other betting events to specific conversion actions.

Additional provider mappings: TikTok and Twitter/X pass betting events through as-is (custom events). Reddit maps bet_placed/first_bet → Purchase, others as custom. Pinterest maps bet_placed/first_bet → checkout, others as-is. Microsoft Ads maps bet_placed/first_bet → purchase, others as-is.

Property Mapping

Different providers expect different field names for betting properties. mytart automatically maps standard property names to provider-specific conventions while preserving the original properties for custom analytics.

Default property mappings:

| Property | GA4 | Meta Pixel | TikTok | |----------|-----|------------|--------| | stake_amount | → value | → value | → value | | currency | → currency | → currency | → conversion_currency | | value | - | - | → conversion_value |

Example: Automatic mapping preserves both original and mapped fields

await mytart.track({
  event: 'bet_placed',
  properties: {
    stake_amount: 25.0,
    odds: 2.5,
    sport: 'football',
    currency: 'GBP',
  },
});

Sent to GA4: { stake_amount: 25.0, value: 25.0, odds: 2.5, sport: 'football', currency: 'GBP' }
Sent to TikTok: { stake_amount: 25.0, value: 25.0, conversion_value: 25.0, odds: 2.5, sport: 'football', currency: 'GBP', conversion_currency: 'GBP' }

Custom property mappings

You can override default mappings or add custom ones via eventTaxonomy.propertyMappings:

{
  provider: 'google-analytics',
  measurementId: 'G-XXX',
  apiSecret: 'secret',
  eventTaxonomy: {
    propertyMappings: {
      stake_amount: 'value',
      odds: 'custom_odds_field',
      market_type: 'market',
    },
  },
  enabled: true,
}

See the Betting / iGaming Events guide for complete property interfaces, usage examples, and best practices.

Usage examples

// Track a bet placement (typed properties with IDE intellisense)
await analytics.track({
  event: 'bet_placed',
  properties: {
    stake_amount: 25.00,
    odds: 2.5,
    odds_format: 'decimal',
    market_type: 'match_winner',
    sport: 'football',
    league: 'Premier League',
    selection: 'Arsenal to win',
    bet_type: 'single',
    is_live: false,
    bet_id: 'bet-abc-123',
  },
  value: 25.00,
  currency: 'GBP',
  orderId: 'bet-abc-123',
});

// Track a bet settlement
await analytics.track({
  event: 'bet_settled',
  properties: {
    bet_id: 'bet-abc-123',
    settlement_type: 'win',
    payout_amount: 62.50,
    stake_amount: 25.00,
    odds: 2.5,
    sport: 'football',
  },
});

// Track a deposit
await analytics.track({
  event: 'deposit',
  properties: {
    amount: 100.00,
    payment_method: 'card',
    is_first_deposit: true,
  },
  value: 100.00,
  currency: 'GBP',
});

// Track a responsible gambling alert
await analytics.track({
  event: 'responsible_gambling_alert',
  properties: {
    alert_type: 'deposit_limit_reached',
    limit_amount: 500.00,
    limit_period: 'weekly',
  },
});

Custom mappings for Google Ads

Since only bet_placed has a default Google Ads mapping, use customMappings to map other betting events to your conversion actions:

{
  provider: 'google-ads',
  conversionActionId: 'AW-123456789/AbC1dEfG2hIjK3lM',
  appType: 'browser',
  eventTaxonomy: {
    customMappings: {
      deposit: 'first_deposit',          // map deposit to a custom conversion
      bet_settled: 'bet_win',            // map settlement to a custom conversion
    },
  },
  enabled: true,
}

Bot Filtering

Set ignoreBots: true at the top level to silently drop all track(), identify(), and page() calls when the visitor is a known bot or crawler. Detection is powered by ua-parser-js's isBot() function.

const analytics = new Mytart({
  ignoreBots: true,
  providers: [
    { provider: 'google-analytics', measurementId: 'G-XXXXXXXXXX', enabled: true },
    { provider: 'segment', writeKey: 'YOUR_KEY', enabled: true },
  ],
});

When a bot is detected, all methods return an empty TrackResult[] array — no events are dispatched to any provider.

The User-Agent is read from:

  1. context.userAgent if supplied in the track() call
  2. navigator.userAgent in browser environments

If no User-Agent is available (e.g. server-side without context.userAgent), the call proceeds normally.

Cross-Provider Linking

Set crossProviderLinking: true to automatically capture click IDs and analytics cookies, then inject them as xpl_-prefixed properties into every track(), page(), and identify() call. This lets you correlate events across providers — e.g. trace a Meta ad click through to a Clarity session recording or a Mixpanel funnel.

const analytics = new Mytart({
  crossProviderLinking: true,
  // browserFingerprint (default: true) works alongside cross-provider linking —
  // fingerprint provides a stable anonymousId, linking captures click IDs and cookies
  providers: [
    { provider: 'google-analytics', measurementId: 'G-XXXXXXXXXX', appType: 'browser', enabled: true },
    { provider: 'meta-pixel', pixelId: '123456789', appType: 'browser', enabled: true },
    { provider: 'clarity', projectId: 'YOUR_PROJECT_ID', enabled: true },
    { provider: 'mixpanel', token: 'YOUR_TOKEN', enabled: true },
  ],
});

What gets captured

| Source | Captured ID | Property key | |---|---|---| | ?gclid= URL param | Google click ID | xpl_gclid | | ?fbclid= URL param | Meta click ID | xpl_fbclid | | ?ttclid= URL param | TikTok click ID | xpl_ttclid | | ?msclkid= URL param | Microsoft Ads click ID | xpl_msclkid | | ?li_fat_id= URL param | LinkedIn click ID | xpl_li_fat_id | | _fbp cookie | Meta browser ID | xpl_fbp | | _fbc cookie | Meta click ID cookie | xpl_fbc | | _ga cookie | GA client ID | xpl_ga_client_id |

If fbclid is present in the URL but the _fbc cookie has not yet been set, mytart synthesises an fbc value in Meta's standard format (fb.1.{timestamp}.{fbclid}).

How it flows to each provider

All captured IDs are injected as event properties (track/page) or user traits (identify). No provider code changes are needed — each provider already forwards properties to its API:

  • GA4 — xpl_* fields appear as event parameters and user properties (queryable in BigQuery exports)
  • Meta Pixel — xpl_* fields become custom data parameters (fbclid/_fbp/_fbc are also handled natively by Meta)
  • Clarity — xpl_* fields are set as custom tags, letting you filter session recordings by ad click source
  • Mixpanel / Amplitude / PostHog — xpl_* fields become event and user properties, fully queryable
  • Segment — xpl_* fields flow through to all downstream destinations in your Segment pipeline
  • Plausible — xpl_* fields are sent as custom props (must be registered in your Plausible dashboard to appear in reports)
  • TikTok — xpl_ttclid is extracted and included as ttclid in the Events API user data; other xpl_* fields forwarded as event properties
  • Snapchat — xpl_sccid maps to sc_click_id, xpl_sc_cookie1 to sc_cookie1 in CAPI user_data; other xpl_* fields in custom_data
  • Twitter/X — xpl_twclid maps to twclid identifier in the Conversions API payload
  • Reddit — xpl_* fields forwarded in event_metadata for downstream analysis
  • Pinterest — xpl_epik (Pinterest _epik cookie) maps to click_id in CAPI user_data for attribution matching
  • Microsoft Ads — xpl_msclkid maps to MicrosoftClickId in the Offline Conversion payload — primary attribution key

Inspecting captured IDs

Use getCapturedIds() to see what was captured at construction time:

const ids = analytics.getCapturedIds();
// { fbclid: 'abc123', fbc: 'fb.1.1234567890.abc123', gaClientId: '1234567890.1234567890' }

Returns null when crossProviderLinking is disabled.

Notes

  • Browser-only. SSR-safe — returns empty results when window is undefined.
  • User properties take precedence. If you pass a property with the same xpl_* key, your value wins.
  • Consent. This feature reads existing URL parameters and cookies — it does not set new cookies or tracking identifiers. Each provider's own consent mechanism remains authoritative.

Browser Fingerprint

By default (browserFingerprint: true), mytart uses @thumbmarkjs/thumbmarkjs to generate a stable browser fingerprint and set it as the anonymousId in central state. This provides a consistent, cookieless device identifier that persists across sessions, cache clears, and incognito mode for the same browser/device.

How it works

  1. Lazy resolution — the fingerprint is computed on the first track(), identify(), or page() call, not at construction time. The first call waits for the fingerprint to resolve before dispatching to any provider — so all providers receive the fingerprint as their anonymous/device ID on the very first event. The result is cached for all subsequent calls.
  2. Dynamic import — ThumbmarkJS is loaded via import() for tree-shaking. Server-only consumers never load the library.
  3. SSR-safe — returns undefined when window is unavailable. No errors, no side effects.
  4. Graceful degradation — if ThumbmarkJS fails or is not installed, tracking continues without a fingerprint (caught silently).
  5. Explicit values win — fingerprint only sets anonymousId when no explicit value has been provided via defaultAnonymousId, setAnonymousId(), or per-call anonymousId.
  6. Full result exposed — the complete ThumbmarkJS response (ThumbmarkResponse) is stored in state.fingerprintData and accessible via mytart.getState().fingerprintData. The fingerprint ID hash is also available as state.fingerprintId and via mytart.getFingerprintId() for direct access without digging into fingerprintData.

How it flows to providers

The fingerprint (~64 char hash) flows to all providers as their anonymous/device ID:

| Provider | Field | Notes | |---|---|---| | GA4 | client_id | Both browser and server mode | | Mixpanel | distinct_id | Falls back when no userId | | PostHog | distinct_id | Falls back when no userId | | Amplitude | device_id | Separate from user_id | | Segment | anonymousId | Standard Segment field | | Meta CAPI | external_id | Hashed before sending | | TikTok | external_id | Hashed before sending | | Reddit | external_id | Hashed before sending | | Pinterest | external_id | Hashed, wrapped in array | | Snapchat | xpl_anonymous_id | No dedicated external_id field | | Twitter/X | xpl_anonymous_id | No dedicated external_id field | | Microsoft Ads | xpl_anonymous_id | No dedicated external_id field |

Disabling

Set browserFingerprint: false to disable:

const analytics = new Mytart({
  browserFingerprint: false,
  providers: [/* ... */],
});

Provider Groups

Provider groups let you route events to subsets of your providers. Assign providers to groups like 'marketing', 'product', 'infrastructure', or 'physical', then specify which groups should receive each event.

Configuring groups

Add a group field to any provider config:

const analytics = new Mytart({
  providers: [
    { provider: 'google-analytics', measurementId: 'G-XXX', appType: 'browser', enabled: true, group: 'product' },
    { provider: 'meta-pixel', pixelId: '123', appType: 'browser', enabled: true, group: 'marketing' },
    { provider: 'segment', writeKey: 'KEY', enabled: true, group: ['marketing', 'product'] },  // multiple groups
    { provider: 'clarity', projectId: 'ABC', enabled: true, group: 'product' },
    { provider: 'posthog', apiKey: 'phc_KEY', enabled: true },  // no group assigned
  ],
});

Sending events to specific groups

Pass groups on any track(), identify(), or page() call:

// Only marketing providers receive this event (Meta Pixel + Segment)
await analytics.track({
  event: 'purchase',
  properties: { value: 99.99 },
  groups: ['marketing'],
});

// Only product providers (GA4 + Segment + Clarity)
await analytics.page({
  url: 'https://example.com/dashboard',
  groups: ['product'],
});

// Multiple groups — providers in either group receive the event
await analytics.identify({
  userId: 'user-123',
  traits: { email: '[email protected]' },
  groups: ['marketing', 'product'],
});

Behavior

  • No groups specified (or empty array): all enabled providers receive the event — fully backward compatible
  • groups specified: only providers whose group(s) intersect with the requested groups receive the event
  • Ungrouped providers excluded: when groups is specified, providers with no group assigned are skipped (PostHog in the example above)
  • Multi-group providers: a provider with group: ['marketing', 'product'] matches either groups: ['marketing'] or groups: ['product']

Available groups

Four fixed group names: 'marketing' | 'product' | 'infrastructure' | 'physical'

| Group | Typical use | |---|---| | marketing | Ad platforms, conversion tracking (Meta, Google Ads, TikTok, Snapchat, etc.) | | product | Product analytics, session recording (GA4, Mixpanel, Amplitude, PostHog, Clarity, etc.) | | infrastructure | Data warehousing, ETL pipelines (Segment, custom providers) | | physical | In-store / POS / offline conversions |

Retry & Dead-Letter Queue

Server-side analytics events can fail silently due to transient errors (rate limits, network blips, server outages). mytart provides automatic retries with exponential backoff and a dead-letter queue (DLQ) for events that exhaust all retry attempts.

Automatic retries

Pass a retry object on MytartConfig to enable retries for all server-mode providers:

const analytics = new Mytart({
  retry: {
    maxRetries: 3,                              // default: 3
    baseDelay: 1000,                            // default: 1000ms
    maxDelay: 30000,                            // default: 30000ms
    retryableStatusCodes: [429, 500, 502, 503, 504],  // default
  },
  providers: [
    { provider: 'segment', writeKey: 'YOUR_KEY', enabled: true },
    { provider: 'posthog', apiKey: 'phc_YOUR_KEY', enabled: true },
  ],
});

// Or use defaults — just pass an empty object:
const analytics2 = new Mytart({
  retry: {},
  providers: [/* ... */],
});

Omit retry entirely to disable retries (single attempt per request).

How it works:

  • Implemented as an axios response interceptor — transparent to all providers, zero retry code in provider implementations
  • Exponential backoff with jitter: baseDelay * 2^attempt * random(0.5–1.5)
  • Honors Retry-After header for 429 (Too Many Requests) responses
  • Network errors (no response) are always retried
  • Non-retryable status codes (e.g. 400, 401, 403) fail immediately

Dead-letter queue

Events that fail after exhausting all retry attempts are automatically added to an in-memory dead-letter queue. This gives you a second chance to process them.

const analytics = new Mytart({
  retry: {},
  deadLetterMaxSize: 500,  // default: 1000 — oldest entries evicted when full
  onDeadLetter: (event) => {
    // Optional callback — persist to database, send to Sentry, etc.
    console.error('Dead letter:', event.provider, event.error);
  },
  providers: [/* ... */],
});

Only events with retryable: true in their TrackResult are added to the DLQ. Client errors (400, 401, 403) are not queued since retrying them would produce the same result.

Inspecting the queue

const queue = analytics.getDeadLetterQueue();
// Returns a shallow copy of DeadLetterEvent[]
// Each entry has: { id, provider, method, args, error, timestamp, attempts }

Replaying the queue

const result = analytics.replayDeadLetterQueue();
// Returns: { replayed: number, failed: DeadLetterEvent[] }
// Successfully replayed events are removed; failures remain in the queue

Clearing the queue

analytics.clearDeadLetterQueue();

TrackResult enrichment

When retries are enabled, TrackResult includes additional metadata:

const results = await analytics.track({ event: 'purchase', value: 99.99 });

for (const result of results) {
  console.log(result.attempts);   // total HTTP attempts (1 = no retries, 4 = 3 retries)
  console.log(result.retryable);  // true if the failure can be retried
  console.log(result.duration);   // wall-clock time in ms (including retries)
}

These fields are optional and non-breaking — they are undefined when retries are disabled or for browser-mode providers.

Debug Mode

Set debug: true on MytartConfig to activate detailed request/response capture and provider-native validation modes. This is the recommended first step when diagnosing "events aren't showing up" issues.

const analytics = new Mytart({
  debug: true,
  retry: {},
  providers: [
    { provider: 'google-analytics', measurementId: 'G-XXX', apiSecret: 'SECRET', enabled: true },
    { provider: 'segment', writeKey: 'YOUR_KEY', enabled: true },
  ],
});

const results = await analytics.track({ event: 'purchase', value: 42 });

for (const result of results) {
  if (result.debugInfo) {
    console.log(result.debugInfo.requestUrl);      // e.g. 'https://www.google-analytics.com/debug/mp/collect'
    console.log(result.debugInfo.requestPayload);   // the exact JSON sent
    console.log(result.debugInfo.responseBody);     // the provider's response
    console.log(result.debugInfo.responseStatus);   // HTTP status code
    console.log(result.debugInfo.duration);         // ms
    console.log(result.debugInfo.validationErrors); // provider-specific errors (if any)
  }
}

What debug mode activates

The global debug flag cascades to all providers. Provider-level config.debug always takes precedence when explicitly set.

| Provider | Debug behavior | |---|---| | GA4 | Routes server requests to /debug/mp/collect (validation endpoint) | | Google Ads | Sets validate_only: true on API requests (validates without processing) | | Snapchat | Routes server requests to /events/validate (validation endpoint) | | Meta Pixel | Calls fbq('set', 'debug', true) in browser mode (verbose console logging) | | Heap | Calls heap('setDebug', true) in browser mode (verbose console logging) | | All providers | Captures full request/response in TrackResult.debugInfo |

Provider-level override

You can enable debug for a specific provider without affecting others:

const analytics = new Mytart({
  // debug: false (default) — most providers run normally
  providers: [
    {
      provider: 'google-analytics',
      measurementId: 'G-XXX',
      apiSecret: 'SECRET',
      debug: true,  // only GA4 uses the debug endpoint
      enabled: true,
    },
    { provider: 'segment', writeKey: 'YOUR_KEY', enabled: true },
  ],
});

Or disable debug for a specific provider when the global flag is on:

const analytics = new Mytart({
  debug: true,
  providers: [
    {
      provider: 'google-analytics',
      measurementId: 'G-XXX',
      apiSecret: 'SECRET',
      debug: false,  // GA4 uses production endpoint despite global debug
      enabled: true,
    },
    { provider: 'snapchat', pixelId: 'SNAP_ID', accessToken: 'TOKEN', enabled: true },
    // Snapchat will use /events/validate because global debug is true
  ],
});

State Management

Mytart maintains a central state (userId, anonymousId, sessionId) that all providers can access. This enables consistent user identification across all analytics calls without manually passing IDs to every method.

How it works

  1. Initial state is set from defaultUserId, defaultAnonymousId, and defaultSessionId in the config
  2. identify() updates state — when you call identify({ userId: 'abc' }), the state is updated BEFORE dispatching to providers, ensuring all providers receive the updated userId
  3. All methods use state — track(), page(), and identify() all receive the current state values (user-provided values override state defaults)

State methods

const analytics = new Mytart({
  defaultUserId: 'default-user',
  defaultAnonymousId: 'anon-123',  // explicit value — overrides browserFingerprint
  defaultSessionId: 'session-abc',
  providers: [{ provider: 'segment', writeKey: 'YOUR_KEY', enabled: true }],
});

// Get current state
const state = analytics.getState();
// { userId: 'default-user', anonymousId: 'anon-123', sessionId: 'session-abc' }

// Set individual IDs
analytics.setUserId('user-456');
analytics.setAnonymousId('anon-789');
analytics.setSessionId('session-xyz');

// Clear userId (e.g., on logout)
analytics.clearUserId();

// Clear session only (e.g., when session expires)
analytics.clearSessionId();

// Clear all state
analytics.clearState();

// identify() also updates state
await analytics.identify({ userId: 'user-789', traits: { email: '[email protected]' } });
// State is now: { userId: 'user-789', ... }

How state flows to providers

All providers receive the current state in their track(), identify(), and page() calls. Each provider maps state to its API correctly:

  • GA4 — user_id and session_id in Measurement Protocol and gtag.js event params
  • Google Ads (browser) — user_id set via gtag('set'), email/phone/address as enhanced conversion params
  • Google Ads (server) — hashed user identifiers in user_identifiers[] payload; identify() caches identifiers
  • Segment — userId, anonymousId, sessionId in track/identify/page
  • Amplitude — user_id, device_id, session_id in events
  • PostHog — distinct_id and $session_id in properties
  • Mixpanel — distinct_id and $session_id in events
  • Meta Pixel (server) — external_id in user_data, xpl_anonymous_id and xpl_session_id in custom_data
  • Meta Pixel (browser) — xpl_session_id in event properties
  • Clarity — userId passed to clarity('identify', userId, sessionId, undefined, friendlyName), sessionId also set as custom tag
  • TikTok (server) — external_id in user data (hashed), xpl_anonymous_id and xpl_session_id in event properties, ttclid from cross-provider linking
  • TikTok (browser) — external_id via ttq.identify({ external_id }), email/phone_number also forwarded
  • Snapchat (server) — external_id in user_data (NOT hashed), PII fields hashed via hashUserData(), xpl_anonymous_id and xpl_session_id in custom_data
  • Snapchat (browser) — user_email and user_phone_number via snaptr('init') re-init for advanced matching
  • Twitter (server) — hashed_email and hashed_phone_number in identifiers array (SHA-256), twclid from cross-provider linking
  • Twitter (browser) — email/phone via twq('event', eventTag, { email_address, phone_number }) (auto-hashed by pixel)
  • Reddit (server) — external_id in user data (SHA-256 hashed), email hashed, uuid forwarded unhashed, xpl_anonymous_id/xpl_session_id in metadata
  • Reddit (browser) — externalId and email via rdt('init') re-init for advanced matching
  • Pinterest (server) — external_id in user_data (hashed, wrapped in array), PII fields hashed and wrapped in arrays, click_id (_epik cookie) forwarded unhashed
  • Pinterest (browser) — external_id and em via pintrk('set', { external_id, em }) for enhanced matching
  • Microsoft Ads (server) — HashedEmailAddress and HashedPhoneNumber (SHA-256) in OfflineConversion payload, MicrosoftClickId (msclkid) forwarded unhashed
  • Microsoft Ads (browser) — em and ph (SHA-256 hashed) via uetq.push('set', { pid: { em, ph } }) for Enhanced Conversions
  • Plausible — does not support user identification (privacy-first, by design)

API Reference

new Mytart(config: MytartConfig)

interface MytartConfig {
  providers: ProviderConfig[];
  defaultUserId?: string;       // applied to every track/page call if no userId given
  defaultAnonymousId?: string;  // applied to every track/page call if no anonymousId given
  defaultSessionId?: string;    // applied to every track/page call if no sessionId given
  debug?: boolean;               // activates provider debug/validate modes + debugInfo capture
  ignoreBots?: boolean;          // when true,