Skip to main content

Journey Tracking

DataGlue now tracks complete user journeys - every session, every event, every step from first visit to conversion. All data is stored in localStorage as a backup, and optionally synced to your server.

Features

  • Complete history - Every session and event from first visit
  • Offline-first - Works without server, data stored in localStorage
  • JSON format - Two simple collections: users and events
  • Auto-identification - Detects email from forms or URL params
  • First & last touch - Attribution tracking built-in
  • Form-friendly - Works with native forms, Fillout, Typeform, etc.

Quick Start

Step 1: Add Profile Endpoint

<script
  src="https://api.dataglue.io/glue.min.js"
  profile-endpoint="https://your-api.com/glue"
  auto-identify="true"
  track-events="pageview,form_submit,click"
></script>

Step 2: That’s It!

DataGlue automatically:
  • Tracks page views, clicks, form submits
  • Stores everything in localStorage (3 keys):
    • glue_user_profile - User info
    • glue_sessions - All sessions
    • glue_events - All events
  • Auto-identifies users when they fill email fields
  • Sends data to your API endpoint

How It Works

User Journey Flow

Day 1:
  → User lands from Google (utm_source=google)
  → DataGlue generates glue_user_id: "abc-123"
  → Tracks: page_view (/landing)
  → User leaves

Day 3:
  → User returns directly
  → Same glue_user_id: "abc-123" (from localStorage)
  → Tracks: page_view (/blog)
  → User leaves again

Day 5:
  → User clicks email link (utm_source=email)
  → Lands on /promo
  → Fills form with email: john@example.com
  → DataGlue auto-identifies: links "abc-123" → "john@example.com"
  → Form redirects to /thank-you?email=john@example.com&glue_user_id=abc-123
  → Your server receives identify() call
  → Journey complete!

Data Structure

localStorage Keys

glue_user_profile

{
  "glue_user_id": "abc-123-uuid",
  "email": "john@example.com",
  "traits": {
    "fname": "John",
    "lname": "Doe"
  },
  "first_seen": "2024-01-15T10:30:00Z",
  "last_seen": "2024-01-20T16:20:00Z",
  "identified": true
}

glue_sessions

[
  {
    "session_id": "session-001",
    "session_start": "2024-01-15T10:30:00Z",
    "landing_page": "/landing",
    "referrer": "https://google.com",
    "utm": {
      "utm_source": "google",
      "utm_campaign": "summer"
    },
    "pages_viewed": 3,
    "events_count": 5,
    "converted": false
  },
  {
    "session_id": "session-002",
    "session_start": "2024-01-20T16:00:00Z",
    "landing_page": "/promo",
    "utm": {
      "utm_source": "email"
    },
    "pages_viewed": 4,
    "converted": true
  }
]

glue_events

[
  {
    "event_id": "evt-001",
    "session_id": "session-001",
    "event": "page_view",
    "timestamp": "2024-01-15T10:30:00Z",
    "properties": {
      "url": "/landing",
      "title": "Landing Page"
    }
  },
  {
    "event_id": "evt-002",
    "session_id": "session-001",
    "event": "button_click",
    "timestamp": "2024-01-15T10:32:15Z",
    "properties": {
      "button_text": "Learn More"
    }
  },
  {
    "event_id": "evt-003",
    "session_id": "session-002",
    "event": "form_submit",
    "timestamp": "2024-01-20T16:05:00Z",
    "properties": {
      "form_id": "signup"
    }
  }
]

JavaScript API

View Journey Data

// Get complete user journey
const data = glue.journey.getCompleteData();
console.log(data);
/* Returns:
{
  profile: {...},
  sessions: [...],
  events: [...],
  summary: {
    total_sessions: 3,
    total_events: 16,
    converted_sessions: 1,
    attribution: {
      first_touch: { utm_source: "google" },
      last_touch: { utm_source: "email" }
    }
  }
}
*/

// Get just the profile
const profile = glue.journey.getProfile();

// Get all sessions
const sessions = glue.journey.getSessions();

// Get all events
const events = glue.journey.getEvents();

// Export as JSON (for download/debugging)
const json = glue.journey.exportData();
console.log(json); // Pretty-printed JSON string

Manual Tracking

// Track custom event
glue.journey.track('video_played', {
  video_id: '123',
  duration: 45
});

// Manually identify user
glue.journey.identify('user@example.com', {
  fname: 'John',
  lname: 'Doe',
  plan: 'premium'
});

// Clear all journey data
glue.journey.clear();

Server Sync

// Manually send identify to server
await glue.sync.identify('user@example.com', {
  fname: 'John',
  subscription: 'pro'
});

// Manually track event to server
await glue.sync.track('purchase', {
  product_id: '123',
  amount: 99.00
});

// Sync all local data to server
await glue.sync.syncAll();

// Configure sync settings
glue.sync.config({
  autoSync: true,
  syncInterval: 30000 // 30 seconds
});

Server-Side API

Your server needs to handle these endpoints:

POST /glue/identify

Links glue_user_id to email address. Request:
{
  "glue_user_id": "abc-123-uuid",
  "email": "user@example.com",
  "traits": {
    "fname": "John",
    "lname": "Doe"
  },
  "context": {
    "session_id": "session-456",
    "utm_source": "google",
    "utm_campaign": "summer",
    "page": {
      "url": "https://example.com/landing",
      "title": "Landing Page"
    },
    "visitor": {
      "user_agent": "Mozilla/5.0...",
      "geolocation": "US | New York",
      "timezone": "America/New_York"
    }
  }
}
Example Handler (Node.js):
app.post('/glue/identify', async (req, res) => {
  const { glue_user_id, email, traits, context } = req.body;

  // Save to database (MongoDB example)
  await db.users.updateOne(
    { glue_user_id },
    {
      $set: {
        email,
        traits,
        last_seen: new Date(),
        'attribution.last_touch': {
          utm_source: context.utm_source,
          utm_campaign: context.utm_campaign,
          timestamp: new Date()
        }
      },
      $setOnInsert: {
        glue_user_id,
        first_seen: new Date(),
        'attribution.first_touch': {
          utm_source: context.utm_source,
          utm_campaign: context.utm_campaign,
          timestamp: new Date()
        }
      }
    },
    { upsert: true }
  );

  res.json({ success: true });
});

POST /glue/track

Records individual events. Request:
{
  "glue_user_id": "abc-123-uuid",
  "session_id": "session-456",
  "email": "user@example.com",
  "event": "button_click",
  "properties": {
    "button_text": "Get Started",
    "page": "/landing"
  },
  "timestamp": 1704376800000,
  "context": {
    "utm_source": "google"
  }
}
Example Handler:
app.post('/glue/track', async (req, res) => {
  const { glue_user_id, event, properties, timestamp } = req.body;

  // Insert event
  await db.events.insert({
    glue_user_id,
    event,
    properties,
    timestamp: new Date(timestamp)
  });

  // Update user last_seen
  await db.users.updateOne(
    { glue_user_id },
    { $set: { last_seen: new Date() } }
  );

  res.json({ success: true });
});

POST /glue/sync

Receives complete user data (all sessions + events). Request:
{
  "glue_user_id": "abc-123-uuid",
  "data": {
    "profile": {...},
    "sessions": [...],
    "events": [...],
    "summary": {...}
  },
  "synced_at": "2024-01-20T16:30:00Z"
}
Example Handler:
app.post('/glue/sync', async (req, res) => {
  const { glue_user_id, data } = req.body;

  // Save complete user data
  await db.users.updateOne(
    { glue_user_id },
    { $set: { ...data.profile, last_synced: new Date() } },
    { upsert: true }
  );

  // Bulk insert events
  if (data.events.length > 0) {
    await db.events.insertMany(data.events, { ordered: false });
  }

  res.json({ success: true });
});

Configuration Options

Script Tag Attributes

<script
  src="https://api.dataglue.io/glue.min.js"
  profile-endpoint="https://api.example.com/glue"
  auto-identify="true"
  auto-sync="false"
  track-events="pageview,click,form_submit"
></script>
AttributeDefaultDescription
profile-endpoint-Your API base URL
auto-identifytrueAuto-identify from email fields/URL params
auto-syncfalseAutomatically sync data to server
track-eventspageview,form_submitEvents to auto-track (comma-separated)

Event Types

Auto-trackable events:
  • pageview - Page views
  • click - Link and button clicks
  • form_submit - Form submissions

Query Examples

Get User Journey

// Client-side
const journey = glue.journey.getCompleteData();

// Server-side (MongoDB)
const user = await db.users.findOne({ email: "john@example.com" });
const events = await db.events.find({ glue_user_id: user.glue_user_id }).sort({ timestamp: 1 });

Conversion Funnel

// Server-side
const landed = await db.events.countDocuments({
  event: "page_view",
  "properties.path": "/landing"
});

const submitted = await db.events.countDocuments({
  event: "form_submit"
});

const converted = await db.events.countDocuments({
  event: "page_view",
  "properties.path": "/thank-you"
});

console.log(`Funnel: ${landed}${submitted}${converted}`);

Attribution Report

// Server-side
const sources = await db.users.aggregate([
  {
    $group: {
      _id: "$attribution.first_touch.utm_source",
      count: { $sum: 1 }
    }
  }
]);

Best Practices

  1. Let forms redirect with query params - Easiest way to pass data between pages
  2. Use meaningful event names - video_played not event_1
  3. Keep properties simple - JSON-serializable values only
  4. Implement server endpoints - Don’t rely solely on localStorage
  5. Test with different form providers - Fillout, Typeform, native HTML

Troubleshooting

Check localStorage Data

// Open browser console
console.log(JSON.parse(localStorage.getItem('glue_user_profile')));
console.log(JSON.parse(localStorage.getItem('glue_sessions')));
console.log(JSON.parse(localStorage.getItem('glue_events')));

Debug Mode

// Enable detailed logging
localStorage.setItem('glue_dev_mode', 'true');
location.reload();

Export Journey

// Download complete journey as JSON file
const data = glue.journey.exportData();
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'user-journey.json';
a.click();

FAQ

Q: Does this work with iframes? A: Yes! For embedded forms, they redirect with query params. DataGlue on the next page auto-identifies from URL. Q: What if my server is down? A: All data stays in localStorage. When server comes back up, call glue.sync.syncAll(). Q: How much localStorage space does this use? A: ~10-50KB per user. DataGlue limits to 1000 events and 100 sessions automatically. Q: Can I use this with Segment/Mixpanel? A: Yes! Use glue.journey.track() and send to both DataGlue and your analytics tool. Q: How do I handle multiple devices? A: Each device has its own glue_user_id. When email is captured, your server links all IDs to one user.