vTilt
Why vTiltHow It WorksFeaturesFAQDocs
Docs / Identify & alias
Quick startEvent forwarding
MCP server
Guides
OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceGoogle Ads
Client setup
CursorClaude DesktopVS CodeCodex
Realtime
Debug ViewRealtime Dashboard
Integration guides
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
SDK
Browser SDK
InstallScript bundlesEvent trackingAutocaptureIdentify & aliasWeb VitalsSession recordingChat widgetFeature readinessRemote configurationReverse proxyDebug logging
Node SDK
Install & setupCapture, identify & aliasContext & shutdown

Documentation

vTilt
Quick startEvent forwarding

MCP server

Realtime

Debug ViewRealtime Dashboard

Integration guides

SDK

InstallScript bundlesEvent trackingAutocaptureIdentify & aliasWeb VitalsSession recordingChat widgetFeature readinessRemote configurationReverse proxyDebug logging
DocsBrowser SDKIdentify & alias

Identify & alias

Link events to authenticated users — at login, on every authenticated page load, and on logout. Includes the SSR pattern for already-logged-in visitors.

identify() is what turns vTilt from "anonymous traffic" into "people". An anonymous visitor browses with a generated $device_id. The first time you call identify('user-123'), the SDK emits a $identify event carrying both that anonymous id and the new user id; the backend merges the two person records and every later event is attributed to user-123.

Important

Important: The single most common integration bug is calling identify() only on the login event. When a user lands on your site with an existing session cookie (the most common case), no login event fires — so identify() never runs, and that user stays anonymous until they log out and back in. Call identify() on every page load when a user is authenticated, not only at login. The identify itself is idempotent and safe to call repeatedly.

#The three moments to identify

MomentWhat to callWhere it lives in your app
Page load with a logged-in uservt.identify(currentUser.id, { … })Right after vt.init() on every page that has session context. This is the one most apps miss.
Login (or signup)vt.identify(newUser.id, { … })Your login success handler, before the redirect.
Logoutvt.resetUser()Your logout handler, before the redirect or page refresh.

Calling identify() for the same user id on every page load is correct and intended. The SDK shortcuts the network call when nothing has changed.

#Pattern: identify on every authenticated page load

The server already knows whether the request belongs to a logged-in user — your session cookie / auth token tells you. The cleanest pattern is to expose the current user as a tiny global on the rendered page, then have the SDK pick it up immediately after vt.init().

#1. Render the current user into the page

Server-render a small JSON blob into the HTML before any analytics code runs. Use whatever templating language fits your stack — examples:

<!-- Plain HTML / PHP / Twig / Rails / Django -->
<script>
  window.__currentUser = <?php echo json_encode(
      $currentUser
          ? [
              'id'    => $currentUser->id,
              'email' => $currentUser->email,
              'name'  => $currentUser->name,
              'plan'  => $currentUser->plan,
            ]
          : null,
  ); ?>;
</script>
html
<!-- Vue 3 with SSR / Nuxt -->
<script>
  window.__currentUser = {{ JSON.stringify(currentUser ?? null) }};
</script>
html
<!-- Next.js — pass via a server component to a small client island -->
<!-- See `pattern-nextjs` below for the recommended idiom. -->
html

#2. Identify after init, every page load

import { vt } from '@v-tilt/browser'

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

const user = window.__currentUser
if (user && user.id) {
  vt.identify(user.id, {
    email: user.email,
    name: user.name,
    plan: user.plan,
  })
}
typescript

That's it. Anonymous visitors stay anonymous. Authenticated visitors are identified on their very first capture, every page load, regardless of whether a "login" happened in this tab or the cookie carried over from a previous visit.

#3. (Recommended) Identify on the server too, with the Node SDK

For events that originate on the server (background jobs, webhooks, server-side conversion events) call identify() from the Node SDK with the same user id. The browser and Node sides converge on the same person record because they share the user id.

#Pattern: login, signup and logout

Add a single line to each auth handler. These calls augment — not replace — the per-page-load identify.

// On successful login — before the redirect
async function onLoginSuccess(user) {
  vt.identify(user.id, {
    email: user.email,
    name: user.name,
    plan: user.plan,
  })
  vt.capture('user_logged_in', { method: 'password' })
  // … redirect
}

// On signup — alias the freshly-created user with their pre-signup
// anonymous activity (otherwise the funnel before signup is orphaned).
async function onSignupSuccess(user) {
  vt.alias(user.id) // keeps the anonymous device id linked to the new user
  vt.identify(user.id, { email: user.email, plan: 'free' })
  vt.capture('user_signed_up', { method: 'email' })
}

// On logout — clear identity so subsequent events are anonymous again.
function onLogoutSuccess() {
  vt.capture('user_logged_out')
  vt.resetUser()
  // … redirect
}
typescript

#Setting and updating user properties

Person properties (email, plan, role, country) are attached during identify() or independently via setUserProperties(). Both are merged onto the person record server-side; later calls overwrite earlier values for the same key.

vt.identify('user-123', {
  email: 'user@example.com',
  name: 'John Doe',
  plan: 'premium',
})

vt.setUserProperties({
  last_login: new Date().toISOString(),
  login_count: 5,
})
typescript

#Anonymous → identified merge

The first identify() call after a user authenticates is the merge. Internally:

  1. The SDK emits a $identify event with distinct_id = user.id and $anon_distinct_id = device_id.
  2. The backend creates (or updates) a person record for user.id and merges everything previously associated with the device id under that person.
  3. All subsequent events use distinct_id = user.id.

This means a visitor's pre-signup browsing — pageviews, autocapture events, performance metrics — is preserved on the same person once they sign up, as long as identify() runs in the same browser session before they leave.

#Opt-out, consent and reset

// Opt out of all analytics
vt.setConsent({ analytics: false })

// Opt back in
vt.setConsent({ analytics: true })

// Reset identity on logout
vt.resetUser()
typescript

resetUser() clears the cached identity and generates a fresh anonymous $device_id on the next event. Use it on logout so two users sharing a device don't get cross-attributed.

#Pattern: Vue + PHP backend

For a complete worked example showing the SSR identify pattern in a Vue + PHP (Laravel/Symfony/plain) stack — including how to handle SPA route changes — see Integration guides › Vue + PHP.

#Common mistakes

Warning

Warning: Identifying only on the login event. On every subsequent page load with an existing session, the user stays anonymous because no login fires. Always also identify on page load when the server says the user is authenticated.

Warning

Warning: Calling identify() with different ids in the same session. Each new id triggers a merge against the current device id, which can produce unexpected joins. Use alias() if you need to link two known ids; use resetUser() between distinct users on a shared device.

Warning

Warning: Identifying with personally-identifiable strings as the id. The id should be your stable internal user id (UUID, integer, ULID), not an email — you can attach the email as a property. Emails change; ids don't.

PreviousAutocaptureBrowser SDKNextWeb VitalsBrowser SDK

On this page

  • The three moments to identify
  • Pattern: identify on every authenticated page load
  • 1. Render the current user into the page
  • 2. Identify after init, every page load
  • 3. (Recommended) Identify on the server too, with the Node SDK
  • Pattern: login, signup and logout
  • Setting and updating user properties
  • Anonymous → identified merge
  • Opt-out, consent and reset
  • Pattern: Vue + PHP backend
  • Common mistakes