Databuddy uses a privacy-first approach to identify users and sessions without relying on cookies or personal information. Every visitor gets an anonymous ID, and sessions are automatically managed.
Anonymous IDs
Every visitor to your website receives a unique anonymous ID that persists across page views and sessions.
Generation
Anonymous IDs are generated using UUIDv4 and prefixed with anon_:
// From packages/tracker/src/core/tracker.ts:167-169
generateAnonymousId (): string {
return `anon_ ${ generateUUIDv4 () } ` ;
}
Example: anon_550e8400-e29b-41d4-a716-446655440000
Storage
Anonymous IDs are stored in localStorage with the key did (Databuddy ID):
// From packages/tracker/src/core/tracker.ts:145-165
getOrCreateAnonymousId (): string {
if ( this . isServer ()) {
return this . generateAnonymousId ();
}
// Check URL parameter first
const urlParams = new URLSearchParams ( window . location . search );
const anonId = urlParams . get ( "anonId" );
if ( anonId ) {
localStorage . setItem ( "did" , anonId );
return anonId ;
}
// Check existing ID in localStorage
const storedId = localStorage . getItem ( "did" );
if ( storedId ) {
return storedId ;
}
// Generate new ID
const newId = this . generateAnonymousId ();
localStorage . setItem ( "did" , newId );
return newId ;
}
Anonymous IDs persist until the user clears their browser’s localStorage or opts out of tracking.
Cross-Domain Tracking
Pass anonymous IDs between domains using URL parameters:
// On domain A: app.example.com
const anonId = localStorage . getItem ( 'did' );
window . location . href = `https://shop.example.com?anonId= ${ anonId } ` ;
// On domain B: shop.example.com
// Databuddy automatically picks up the anonId from the URL
This allows you to track users across multiple domains while maintaining privacy.
Session IDs
Sessions represent a single visit to your website. A new session starts when:
A user visits your site for the first time
30 minutes of inactivity have passed
The user closes and reopens their browser
Generation
Session IDs use the same UUID format but are prefixed with sess_:
// From packages/tracker/src/core/tracker.ts:204-206
generateSessionId (): string {
return `sess_ ${ generateUUIDv4 () } ` ;
}
Example: sess_a1b2c3d4-e5f6-7890-abcd-ef1234567890
Storage
Session IDs are stored in sessionStorage (not localStorage), which means:
They’re cleared when the tab is closed
They’re unique per browser tab
They don’t persist after the browser closes
// From packages/tracker/src/core/tracker.ts:171-202
getOrCreateSessionId (): string {
if ( this . isServer ()) {
return this . generateSessionId ();
}
// Check URL parameter
const urlParams = new URLSearchParams ( window . location . search );
const sessionIdFromUrl = urlParams . get ( "sessionId" );
if ( sessionIdFromUrl ) {
sessionStorage . setItem ( "did_session" , sessionIdFromUrl );
sessionStorage . setItem ( "did_session_timestamp" , Date . now (). toString ());
return sessionIdFromUrl ;
}
// Check existing session
const storedId = sessionStorage . getItem ( "did_session" );
const sessionTimestamp = sessionStorage . getItem ( "did_session_timestamp" );
if ( storedId && sessionTimestamp ) {
const sessionAge = Date . now () - Number . parseInt ( sessionTimestamp , 10 );
if ( sessionAge < 30 * 60 * 1000 ) { // 30 minutes
sessionStorage . setItem ( "did_session_timestamp" , Date . now (). toString ());
return storedId ;
}
// Session expired, clear storage
sessionStorage . removeItem ( "did_session" );
sessionStorage . removeItem ( "did_session_timestamp" );
sessionStorage . removeItem ( "did_session_start" );
}
// Create new session
const newId = this . generateSessionId ();
sessionStorage . setItem ( "did_session" , newId );
sessionStorage . setItem ( "did_session_timestamp" , Date . now (). toString ());
return newId ;
}
Session Timeout
Sessions automatically expire after 30 minutes of inactivity . The timestamp is updated on each tracked event to keep active sessions alive.
The 30-minute timeout is a common standard used by Google Analytics and other analytics platforms.
Session Start Time
Databuddy tracks when each session started:
// From packages/tracker/src/core/tracker.ts:208-221
getSessionStartTime (): number {
if ( this . isServer ()) {
return Date . now ();
}
const storedTime = sessionStorage . getItem ( "did_session_start" );
if ( storedTime ) {
return Number . parseInt ( storedTime , 10 );
}
const now = Date . now ();
sessionStorage . setItem ( "did_session_start" , now . toString ());
return now ;
}
This allows you to:
Calculate session duration
Track time to conversion
Analyze engagement patterns
User vs Session Metrics
Understand the difference between user and session metrics:
Counted by anonymous ID . Represents the number of distinct visitors across all time periods. SELECT uniq(anonymous_id) as unique_users
FROM analytics . events
WHERE time >= now () - INTERVAL 7 DAY ;
Counted by session ID . Represents the number of distinct visits. SELECT uniq(session_id) as sessions
FROM analytics . events
WHERE time >= now () - INTERVAL 7 DAY ;
Total number of pages viewed. One user can generate multiple page views per session. SELECT count ( * ) as pageviews
FROM analytics . events
WHERE event_name = 'screen_view'
AND time >= now () - INTERVAL 7 DAY ;
Users with more than one session. Calculated by comparing session counts per anonymous ID. SELECT countIf(session_count > 1 ) as returning_users
FROM (
SELECT anonymous_id, uniq(session_id) as session_count
FROM analytics . events
GROUP BY anonymous_id
);
Activity Tracking
Databuddy tracks various activity metrics within each session:
Page Count
The number of pages viewed in the current session:
pageCount = 0 ;
// Incremented on each page view
this . pageCount ++ ;
Interaction Count
The number of user interactions (clicks, form inputs, etc.):
interactionCount = 0 ;
// Incremented on tracked interactions
this . interactionCount ++ ;
Engaged Time
Time spent actively engaging with the page (visible and active):
// From packages/tracker/src/core/tracker.ts:596-624
startEngagement (): void {
if ( this . engagementStartTime === null ) {
this . engagementStartTime = Date . now ();
this . isPageVisible = true ;
}
}
pauseEngagement (): void {
if ( this . engagementStartTime !== null ) {
this . engagedTime += Date . now () - this . engagementStartTime ;
this . engagementStartTime = null ;
this . isPageVisible = false ;
}
}
getEngagedTime (): number {
let total = this . engagedTime ;
if ( this . engagementStartTime !== null ) {
total += Date . now () - this . engagementStartTime ;
}
return total ;
}
Engaged time is paused when:
The page loses focus
The user switches tabs
The page is hidden
User Identification (Optional)
While Databuddy is anonymous by default, you can optionally identify users after authentication:
Only use user identification if you have proper consent and privacy policies in place. Never send PII without user consent.
// After user logs in
window . db . track ( 'user_identified' , {
userId: 'user_12345' , // Your internal user ID
plan: 'pro'
});
Best practices for user identification:
Use hashed IDs : Hash email addresses or use internal database IDs
Store separately : Keep PII in PostgreSQL, not ClickHouse
Get consent : Clearly inform users about identification
Allow unlinking : Provide a way to disassociate the anonymous ID
Session Attributes
Every event in a session includes:
{
anonymousId : "anon_550e8400-e29b-41d4-a716-446655440000" ,
sessionId : "sess_a1b2c3d4-e5f6-7890-abcd-ef1234567890" ,
sessionStartTime : 1709251200000 ,
timestamp : 1709251320000 ,
pageCount : 3 ,
interactionCount : 7 ,
scrollDepth : 85 ,
timeOnPage : 120000 , // 120 seconds
}
Multi-Tab Behavior
Each browser tab gets its own session:
User opens site in Tab A
New session created: sess_abc123
User opens site in Tab B
New session created: sess_def456
Both tabs active
Two separate sessions tracked simultaneously with the same anonymous ID
This provides accurate tracking of multi-tab browsing behavior.
Session Replay (Coming Soon)
Databuddy will soon support privacy-friendly session replay:
Text content masked by default
Input fields never recorded
Opt-in per session
Full GDPR compliance
Database Schema
Sessions and users are tracked in the ClickHouse events table:
-- From packages/db/src/clickhouse/schema.ts
CREATE TABLE analytics .events (
id UUID,
client_id String,
anonymous_id String, -- User identifier
session_id String, -- Session identifier
session_start_time Nullable(DateTime64( 3 , 'UTC' )),
time DateTime64( 3 , 'UTC' ),
page_count UInt8 DEFAULT 1 ,
interaction_count Nullable(Int16),
time_on_page Nullable(Float32),
scroll_depth Nullable(Float32),
-- Other event data...
) ENGINE = MergeTree()
PARTITION BY toYYYYMM( time )
ORDER BY (client_id, time , id);
Querying Sessions
Examples of common session queries:
Session Duration
Pages Per Session
Bounce Rate
User Retention
SELECT
session_id,
max ( time ) - min ( time ) as session_duration_ms,
session_duration_ms / 1000 as session_duration_sec
FROM analytics . events
WHERE client_id = 'your-client-id'
AND time >= now () - INTERVAL 7 DAY
GROUP BY session_id;
Best Practices
Don't Reset IDs Never manually reset anonymous or session IDs. Let Databuddy manage them automatically.
Use URL Parameters For cross-domain tracking, pass IDs via URL parameters: https://example.com?anonId=anon_xxx&sessionId=sess_yyy
Respect Privacy Don’t correlate anonymous IDs with PII without explicit user consent.
Monitor Session Length Track average session duration to understand engagement: SELECT avg (duration) FROM sessions
Learn More
Privacy First Learn about Databuddy’s privacy architecture
Event Tracking Understand how events are captured and sent
Data Model Explore the complete database schema