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 / Cloudflare Workers
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
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails

API Reference

MCP server

On this page

On this page

  • 1. Install
  • 2. Module worker
  • 3. Identify users
  • 4. Scheduled events
  • 5. As a reverse proxy
  • Next steps
DocsGuidesBackend frameworksCloudflare Workers

Cloudflare Workers

Last updated July 22, 2026

Integrate vTilt with Cloudflare Workers — Node SDK on the workerd runtime, waitUntil() to flush events before the worker freezes.

Cloudflare Workers run on the workerd runtime, which supports modern fetch-based libraries like @v-tilt/node. The catch: workers freeze the moment your handler returns, so you must call ctx.waitUntil(client.shutdown()) to make sure batched events actually flush.

#1. Install

npm install @v-tilt/node
bash
# wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat"]

[vars]
VTILT_HOST = "https://www.vtilt.com"

# secrets — set via `wrangler secret put VTILT_TRACKER_TOKEN`
toml

#2. Module worker

// src/index.ts
import { VTiltNode } from '@v-tilt/node'

interface Env {
  VTILT_TRACKER_TOKEN: string
  VTILT_HOST: string
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext,
  ): Promise<Response> {
    const vtilt = new VTiltNode(env.VTILT_TRACKER_TOKEN, {
      host: env.VTILT_HOST,
    })

    const url = new URL(request.url)
    if (url.pathname === '/track' && request.method === 'POST') {
      const { userId, event, properties } = await request.json<{
        userId: string
        event: string
        properties: Record<string, unknown>
      }>()

      vtilt.capture({ distinctId: userId, event, properties })
    }

    // Crucial: flush before the worker is frozen.
    ctx.waitUntil(vtilt.shutdown())

    return new Response(JSON.stringify({ ok: true }), {
      headers: { 'content-type': 'application/json' },
    })
  },
}
typescript

Important

Constructing a fresh VTiltNode per request is fine — it's a small object. Reusing a module-scope singleton is also valid because workers can serve multiple requests on the same isolate, but you still need ctx.waitUntil(shutdown()) on every request to flush its buffered events.

#3. Identify users

If your worker sits in front of an authenticated app, identify on every request so server-side captures merge with the browser session.

const userId = await getUserIdFromToken(request.headers.get('authorization'))
if (userId) {
  vtilt.identify({
    distinctId: userId,
    anonymousId: getAnonCookie(request),
    properties: {
      /* ... */
    },
  })
}
typescript

#4. Scheduled events

Workers can also emit events from cron triggers. Same pattern — flush via ctx.waitUntil.

export default {
  async scheduled(
    _controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext,
  ) {
    const vtilt = new VTiltNode(env.VTILT_TRACKER_TOKEN, {
      host: env.VTILT_HOST,
    })

    vtilt.capture({
      distinctId: 'system',
      event: 'cron_ran',
      properties: { job: 'daily-summary' },
    })

    ctx.waitUntil(vtilt.shutdown())
  },
}
typescript

#5. As a reverse proxy

A common pattern is to proxy vTilt traffic through your own domain so ad blockers can't strip analytics. See Reverse proxy for a worker-based proxy example that forwards /_vt/* to your vTilt origin.

#Next steps

  • Reverse proxy — block-resistant ingestion via your own domain.
  • Node SDK / Context & shutdown — request-scoped identity.
  • Hono integration — same SDK with framework routing.
  • Common mistakes — integration pitfalls to avoid before you ship.
PreviousHonoIntegration guidesNextDjangoIntegration guides