askbowtie

Tracking reference

One tag tracks everything automatic. Four calls cover everything custom: events, conversions with revenue, errors, guardrails. All of it lands in one database your AI agent queries over MCP.

Agent-readable version: /docs/events.md

Install

One tag, in <head>, on every page:

<script src="https://askbowtie.com/bowtie.js" async></script>

async, not defer: the tracker catches errors that happen while the page is still parsing. Never version the URL. The domain must be registered at askbowtie.com, otherwise the tracker disables itself.

Tracked automatically

SignalEvent types
Page views, sessionspage_load session_start page_hidden
Clicksclick external_link_click rage_click
Formsform_submit
Errorsconsole_error resource_error network_error csp_violation
Performanceweb_vitals (LCP, CLS, INP, TTFB)

UTM parameters and ad click IDs (gclid, msclkid, fbclid) are captured on landing and survive internal navigation. No cookies. Input values are never read; PII in query strings is redacted before sending.

Public API

The API lives at window.bowtie once the script loads. The script is async, so guard calls that might run before load: window.bowtie?.track(...). Inside user-action handlers (clicks, submits) the tracker is always ready.

Custom events

bowtie.track('signup_started', { source: 'homepage_cta' });
bowtie.track('plan_selected', { plan: 'agency', seats: 5 });

First argument: any string you choose. Second: a flat object of context, stored with the event and queryable later.

Conversions and revenue

bowtie.converted('purchase', { value: 99.00, currency: 'USD', transaction_id: 'ord_1842' });
bowtie.converted('lead_captured', { source: 'contact_form' });
bowtie.converted('trial_started');
FieldTypeNotes
valuenumberRevenue or goal value. Optional.
currencystringISO code, default USD
transaction_idstringDedupe key: the same conversion never counts twice
pixelsboolean / stringAlso fire to connected ad platforms. true = primary Google Ads label, 'secondary' = secondary label. Default off.
email, phonestringEnhanced conversions, hashed by gtag before leaving the page. Only used when pixels fires.

Any other keys you pass are stored with the event.

Visitor identity (cross-session funnels)

bowtie.identify('u_8f3a91');   // after signup/login — your user id or a hashed email
bowtie.identify(null);         // on logout — forget the visitor

By default askbowtie is cookieless: sessions don't connect across visits, so a funnel that completes days later (signup today, purchase from your reminder email next week) reads as incomplete. identify() fixes that with your identifier — it persists in localStorage, rides every event, and visitor-scoped funnels stitch the return visit to the original one.

Zero-code alternative: add ?ab_uid=u_8f3a91 to links in your return emails — the tracker picks it up on landing, same effect.

The identifier is yours, which means the consent story is yours too: calling identify() makes cross-session tracking happen under your user relationship. Sites that never call it keep the cookieless, no-banner default. Prefer an internal id or a hashed email over raw PII.

Application errors

bowtie.error('payment_declined', { reason: 'insufficient_funds', amount: 99.00 });
bowtie.error('validation_failed', { field: 'email' });

For errors your own code catches. Uncaught JS errors, failed resources, and failed requests are already captured automatically.

Guardrails

bowtie.guardrail('rate_limited', { endpoint: '/api/checkout', limit: '10/min' });
bowtie.guardrail('geo_blocked', { country: 'XX' });

Intentional blocks that are not failures: rate limits, quota, access denied, feature flags. Kept separate from errors so your error rate stays honest.

Utilities

bowtie.getSessionId()  // current session id, for support and debugging
bowtie.flush()         // send the queue now, before a hard navigation
bowtie.debug(true)     // tag this tab's events debug:true, persists across pages
Migrating from itbroke.dev? window.itbroke is an alias of window.bowtie. Existing inline calls keep working unchanged.

Funnels (flows)

Define the path a visitor should take once — pages and events, in order — and every funnel question becomes one call. Define it here in the app (/app/flows) or let your agent do it conversationally (define_flow over MCP).

define_flow('quiz funnel', steps: [
  { kind: 'page',  match: '/quiz' },
  { kind: 'event', match: 'quiz_step_2' },
  { kind: 'event', match: 'signup' }
])

Evaluation is order-aware (step 2 only counts after step 1), shows fall-off and median time per step, incidents on each step's page, a baseline window, and flags steps that are too new to compare. Periods include @last-deploy — "did the thing I just shipped help?"

Variants and splits

// One event name, many variants — filter by payload field:
{ kind: 'event', match: 'popup_shown', where: [{ field: 'popup_id', op: 'eq', value: 'exit-intent' }] }

// Same funnel, paid vs organic (first-touch, mutually exclusive):
get_flow('quiz funnel', segment: [{ field: 'tracking.utm_medium', op: 'eq', value: 'cpc' }])
get_flow('quiz funnel', segment: [{ field: 'tracking.utm_medium', op: 'not_exists' }])

Ops: eq, neq, in, contains, exists, not_exists, gt, gte, lt, lte. A step's match also takes an array for any-of matching.

Return visits

Funnels are session-scoped by default. If your visitors complete later (purchase from a reminder email days after signing up), pass scope: 'visitor' — it stitches sessions through visitor identity.

Querying the data

Everything above is queryable by an AI agent through MCP:

claude mcp add --transport http askbowtie https://askbowtie.com/mcp \
  --header "Authorization: Bearer YOUR_TOKEN"

Tools: list_domains, get_summary, get_traffic, get_conversions, get_incidents, get_alerts, get_performance, get_page. Custom events, revenue, errors, and guardrails come back with their data fields intact.