vTilt
By roleFoundersKnow what to fix firstMarketersSee what happens after the clickCMOsSee what your budget really buys
By businessSaaSTurn signups into active usersEcommerceSee why shoppers abandon cartsAgenciesProve your work to clientsEnterpriseOne customer record for every team
MeasureWeb AnalyticsFind where buyers leaveSession ReplaySee why they left, not just that they didAdsKnow what paid visitors do nextIntegrationsOne snippet feeds your stack
ThinkPeopleOne profile per customerCustomer MemoryvTilt remembers every visitorSite KnowledgeAnswers and audits from your own pages
ActAsk AIAnswers from real behaviorAI ChatChat that knows the visitorEmail CampaignsEmail from real visits
PricingWhy vTiltDocs
Docs / Event tracking
Getting Started
OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes
Guides
Event forwardingReverse proxyRealtime dashboardSite knowledge
Frontend frameworks
Next.jsNuxt.jsVue.jsReactReact RouterRemixGatsbySvelte / SvelteKitAstroAngularTanStack StartDocusaurus
Backend frameworks
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails
Backend languages
PythonPHPRubyElixirGoJava.NET / C#Rust
Stack guides
Vue + PHP
API Reference
Browser SDK
Script bundlesAutocaptureWeb VitalsSession recordingChat widgetFeature readinessRemote configurationDebug logging
Node SDK
Install & setupCapture, identify & aliasContext & shutdownGlobal properties & opt-outError tracking
MCP server
Guides
OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceSite knowledgeGoogle AdsMeta Ads
Client setup
CursorClaude DesktopVS CodeCodex

Getting Started

OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes

Guides

Event forwardingReverse proxyRealtime dashboardSite knowledge

API Reference

MCP server

On this page

On this page

  • Bots and crawlers
  • How delivery works
  • Capture events
  • Standard properties
  • When to use a custom event vs autocapture
DocsGetting StartedTrack events

Event tracking

Last updated July 22, 2026

Capture custom events with vt.capture() — how delivery, batching, and Google Tag forwarding work end to end.

Track custom events and attach properties for analysis. Each capture is validated and enriched, then sent to vTilt over HTTP and (when you use the Google Tag destination) mirrored to gtag in the browser. Both paths respect the same "remote config committed" signal so dashboard toggles and mappings apply consistently.

#Bots and crawlers

By default, vTilt does not record bot traffic — the same approach as PostHog.

  • In the browser — Before an event is queued, the SDK checks navigator.userAgent, Client Hints brands, and navigator.webdriver against a built-in list (search engines, SEO tools, AI crawlers, headless browsers, and similar). Matching sessions never call /api/e.
  • On the server — Crawlers that probe your ingest URL directly (without running JavaScript) receive 204 No Content and are not stored.

To disable filtering (for example, to debug a headless test harness):

vt.init('YOUR_PROJECT_TOKEN', {
  api_host: 'https://www.vtilt.com',
  opt_out_useragent_filter: true,
})
typescript

To block an extra user agent substring:

vt.init('YOUR_PROJECT_TOKEN', {
  api_host: 'https://www.vtilt.com',
  custom_blocked_useragents: ['my-internal-crawler'],
})
typescript

Check the current session in DevTools: vt._is_bot() returns true when the SDK would drop captures.

#How delivery works

Ingest and client-side Google forwarding are parallel; they do not block each other.

  • HTTP ingest — After rate limiting, events go through an EventBuffer until __remote_config_loaded (a fresh /api/d merge or fetch failure). Then they batch to /api/e. A safety timeout can flush earlier with $config_pending so the server can still filter.
  • Google Tag (browser) — The SDK subscribes to captures and forwards through gtag when the Google Tag destination is enabled. Until __remote_config_loaded, captures are held in a small bounded queue (max 100). After that, the official dataLayer / gtag shim queues measurement calls until gtag.js finishes loading — no duplicate SDK queue for that phase.

#Capture events

Use vt.capture() to send custom events with optional properties. Names should be snake_case verbs that read like a sentence (button_clicked, purchase_completed).

vt.capture('button_clicked', {
  button_name: 'Sign Up',
  page: 'homepage',
})

vt.capture('purchase_completed', {
  product_id: 'SKU-123',
  price: 99.99,
  currency: 'USD',
  quantity: 1,
})
typescript

#Standard properties

Every capture is automatically enriched with:

PropertyMeaning
$current_urlFull URL at the time of capture.
$pathnameJust the path (/products/123).
$referrerdocument.referrer at capture time ($direct when there is none), plus a derived $referring_domain.
$session_idSticky session identifier (30-minute idle window).
$device_idStable anonymous device identifier.
$distinct_idIdentified user id, or the anonymous device id pre-identify().
engagement_time_msecMilliseconds since the previous enqueued event in this tab (clamped 1 ms – 1 hour). Used by GA4 Measurement Protocol.
$pageleave_*Set on $pageleave events: $prev_pageview_duration (seconds), $prev_pageview_pathname, $prev_pageview_url, plus navigation_type (pagehide / visibility_hidden / spa_transition).

Note

The event-level $referrer is the page the visitor came from for this capture — after the first in-session navigation it is usually a page on your own site. For acquisition / first-touch ("where did this person originally come from?"), read the person's $initial_referrer / $initial_referring_domain (and $initial_utm_*, $initial_gclid, …), which vTilt locks to the visitor's first event. See Identify & person properties.

#When to use a custom event vs autocapture

  • Use autocapture for clicks, form submits, page transitions — anything UI-shaped where the element itself describes the interaction.
  • Use vt.capture() when an event represents a domain action (a purchase, an upgrade, a feature toggle) that doesn't map neatly to a DOM element.

For business-critical events, prefer explicit captures even if autocapture would work — it makes the analysis surface (event names, property schema) deliberate instead of accidental.

PreviousIdentify usersGetting StartedNextLogout & resetGetting Started