# API Overview Source: https://dataglue.io/api-reference/overview Complete reference for DataGlue's JavaScript API methods and properties. # API Reference DataGlue provides a comprehensive JavaScript API for interacting with visitor data and customizing behavior. ## Global Object DataGlue is available globally as `window.glue`: ```javascript theme={null} // Check if DataGlue is loaded if (window.glue) { console.log('DataGlue is ready'); } ``` ## Core Methods ### `glue.storage` Storage management methods: ```javascript theme={null} // Set data window.glue.storage.set('key', 'value'); window.glue.storage.set('key', 'value', 30); // With 30-day expiration // Get data const value = window.glue.storage.get('key'); // Remove data window.glue.storage.remove('key'); // Clear all data window.glue.storage.clear(); ``` ### `glue.getVisitorContext()` Get complete visitor information: ```javascript theme={null} const context = window.glue.getVisitorContext(); console.log(context); // Returns: user agent, language, attribution data, etc. ``` ### `glue.getAttribution()` Get attribution data only: ```javascript theme={null} const attribution = window.glue.getAttribution(); console.log(attribution); // Returns: UTM parameters, click IDs, referrer data ``` ### `glue.dynamicContent()` Configure conditional content: ```javascript theme={null} glue.dynamicContent({ selector: '#premium-content', key: 'utm_source', value: 'email', access_granted: '

Welcome email subscriber!

', access_denied: '

Subscribe to see this content.

' }); ``` ## Configuration ### Script Attributes Configure DataGlue via script tag attributes: ```html theme={null} ``` ### JavaScript Configuration ```javascript theme={null} // Set attribution manually window.glue.setAttribution({ utm_source: 'newsletter', utm_campaign: 'summer_sale' }); ``` ## Events DataGlue emits custom events: ```javascript theme={null} // Listen for initialization window.addEventListener('dataglue:ready', function() { console.log('DataGlue initialized'); }); // Listen for data updates window.addEventListener('dataglue:update', function(event) { console.log('Data updated:', event.detail); }); ``` ## Error Handling ```javascript theme={null} try { const data = window.glue.storage.get('key'); if (!data) { console.warn('No data found for key'); } } catch (error) { console.error('DataGlue error:', error); } ``` # Architecture Source: https://dataglue.io/concepts/architecture Understanding DataGlue's system architecture and data flow patterns. # Architecture DataGlue follows a modular architecture designed for flexibility, performance, and privacy compliance. ## System Overview ```mermaid theme={null} graph TB subgraph "Browser Environment" A[DataGlue Core] B[Storage Layer] C[Attribution Engine] D[Form Integration] E[Dynamic Content] end subgraph "Data Sources" F[URL Parameters] G[User Interactions] H[Third-party APIs] end subgraph "Output Destinations" I[Forms] J[Analytics] K[CRM Systems] end F --> A G --> A H --> A A --> B A --> C A --> D A --> E D --> I C --> J B --> K ``` ## Core Components ### Storage Layer * **localStorage**: Persistent user data * **sessionStorage**: Session-specific data * **Cookies**: Cross-tab synchronization ### Attribution Engine * Multi-touch attribution tracking * UTM parameter management * Third-party platform integration ### Form Integration * Automatic field population * Element selector patterns * Fillout form enhancement ### Dynamic Content * Conditional content display * User attribute evaluation * Real-time personalization ## Data Flow 1. **Collection**: Gather data from URLs, interactions, and APIs 2. **Processing**: Normalize and validate collected data 3. **Storage**: Persist data across multiple storage mechanisms 4. **Application**: Use data to enhance user experience ## Security & Privacy DataGlue is built with privacy-first principles: * Client-side data processing * Configurable data collection * GDPR/CCPA compliance * No server-side data storage by default # Attribution Source: https://dataglue.io/concepts/attribution Understanding DataGlue's multi-touch attribution system and how it tracks customer journeys. # Attribution DataGlue provides comprehensive multi-touch attribution tracking that captures both first-touch and last-touch interactions. ## Attribution Model Unlike traditional analytics that only track last-touch attribution, DataGlue preserves the complete customer journey: ### First-Touch Attribution Records the initial source that brought the visitor to your site: * `glue_initial_utm_source` * `glue_initial_utm_campaign` * `glue_initial_utm_medium` ### Last-Touch Attribution Tracks the most recent source before conversion: * `glue_last_utm_source` * `glue_last_utm_campaign` * `glue_last_utm_medium` ## Customer Journey Example ```mermaid theme={null} journey title Multi-Touch Attribution Journey section Week 1 LinkedIn Ad Click: 5: Visitor Initial UTM Captured: 5: DataGlue Browses Site: 4: Visitor Leaves Without Converting: 3: Visitor section Week 2 Email Campaign Click: 4: Visitor Last UTM Updated: 5: DataGlue Initial UTM Preserved: 5: DataGlue Form Submission: 5: Visitor Both Attributions Sent: 5: DataGlue ``` ## Platform-Specific Attribution DataGlue automatically captures platform-specific identifiers: ### Google Ads * **gclid**: Google click identifier * Automatic capture from URL parameters * Stored as `glue_initial_gclid` and `glue_last_gclid` ### Facebook Ads * **fbclid**: Facebook click identifier * **\_fbp**: Facebook browser pixel * **\_fbc**: Facebook click ID from cookies ### TikTok Ads * **ttid**: TikTok tracking identifier * Session storage synchronization * Enhanced attribution data ## Implementation DataGlue attribution works automatically once installed: ```html theme={null} ``` ## Accessing Attribution Data ```javascript theme={null} // Get all attribution data const attribution = window.glue.getAttribution(); // Get specific touchpoints const firstTouch = window.glue.storage.get('glue_initial_utm_source'); const lastTouch = window.glue.storage.get('glue_last_utm_source'); ``` ## Benefits 1. **Complete Journey Mapping**: See the full path to conversion 2. **Accurate ROI Calculation**: Credit all contributing touchpoints 3. **Campaign Optimization**: Understand which combinations drive results 4. **Customer Insights**: Identify high-value user paths # Form Integration Source: https://dataglue.io/features/form-integration How to integrate DataGlue with forms for automatic field population and data capture. # Form Integration DataGlue automatically populates form fields with stored visitor data, reducing friction and improving conversion rates. ## Basic Setup Add `glue` attributes to your form fields: ```html theme={null}
``` ## Selector Patterns DataGlue recognizes multiple selector patterns: ### Direct Glue Attributes ```html theme={null} ``` ### ID-based Selectors ```html theme={null} ``` ### Class-based Selectors ```html theme={null}
``` ## Fillout Integration For Fillout forms, DataGlue automatically adds data attributes: ```html theme={null}
``` ## Advanced Configuration ### Multiple Forms ```html theme={null}
``` ### Conditional Fields ```javascript theme={null} // Show/hide fields based on visitor data if (window.glue.storage.get('glue_user_tier') === 'premium') { document.getElementById('premium-fields').style.display = 'block'; } ``` ## Best Practices 1. **Use Consistent Naming**: Stick to `glue_` prefix for custom attributes 2. **Test Selectors**: Verify form fields populate correctly 3. **Handle Missing Data**: Provide fallbacks for empty fields 4. **Privacy Compliance**: Only collect necessary information # The Powerhouse of Behavioural Web Data Creation Source: https://dataglue.io/index DataGlue is the powerhouse of behavioural web data creation, providing comprehensive visitor tracking and attribution. DataGlue Hero Light # DataGlue # The Powerhouse of Behavioural Web Data Creation DataGlue is a cutting-edge tracking solution that transcends the capabilities of traditional analytics tools. It is designed to create and track behavioural data of website visitors, offering businesses a comprehensive view of the customer journey. Capture both first-touch and last-touch attribution to understand the complete customer journey. Automatically populate forms with visitor data and track form interactions seamlessly. Show personalized content based on visitor attributes and behavioral data. Built-in GDPR and CCPA compliance with configurable data collection. ## The Digital Challenge The digital landscape is a complex web of customer interactions across multiple touchpoints. Traditional analytics tools often yield fragmented or superficial data, leaving businesses grappling with: Most analytics platforms only track last-touch attribution, missing crucial touchpoints in the customer journey that influence conversions. Customer data gets trapped in different systems, making it impossible to create a unified view of the customer journey. Users abandon forms because they have to re-enter information they've already provided, leading to lost conversions. Without proper visitor context, websites can't provide personalized experiences that drive engagement and conversions. ## The DataGlue Solution DataGlue addresses these challenges by providing: ### 360-Degree Visitor Tracking ```mermaid theme={null} graph LR subgraph "Data Sources" A[Website Visitors] B[UTM Parameters] C[Form Submissions] D[Social Media Clicks] end subgraph "DataGlue Processing" E[Attribution Tracking] F[Behavioral Analysis] G[Identity Resolution] H[Data Enrichment] end subgraph "Output Destinations" I[CRM Systems] J[Google Analytics] K[Marketing Platforms] L[Custom Webhooks] end A --> E B --> E C --> F D --> F E --> G F --> G G --> H H --> I H --> J H --> K H --> L ``` ### Key Features DataGlue captures **both first-touch and last-touch attribution**, preserving the complete customer journey across sessions and devices. ```javascript theme={null} // Example: User's complete attribution history { "initial_utm_source": "linkedin", "initial_utm_campaign": "product_launch", "last_utm_source": "email", "last_utm_campaign": "nurture_sequence", "user_id": "01234567-89ab-cdef-0123-456789abcdef" } ``` Forms automatically populate with stored visitor data, reducing friction and improving conversion rates. ```html theme={null} ``` Show different content based on visitor attributes, creating personalized experiences. ```javascript theme={null} glue.dynamicContent({ selector: '#premium-content', key: 'utm_source', value: 'email', access_granted: '

Exclusive Content for Email Subscribers!

' }); ```
## Real-World Impact **Attribution Recovery**: Businesses using DataGlue typically recover 30-40% of attribution data that was previously lost to last-touch-only tracking. **Privacy Compliance**: DataGlue is designed with privacy-first principles, ensuring GDPR and CCPA compliance out of the box. ### Customer Journey Example Consider this typical user journey that DataGlue automatically tracks: User clicks LinkedIn ad → `glue_initial_utm_source: linkedin` Browses product pages → Session data and engagement tracked Leaves without converting → Attribution data preserved Returns via email campaign → `glue_last_utm_source: email` Submits contact form → Both LinkedIn AND email attribution included **Result**: Your CRM receives a lead with complete multi-touch attribution, enabling accurate ROI calculation and personalized follow-up. ## Getting Started Ready to unlock the power of behavioral data? Get started with DataGlue in minutes: Get DataGlue running on your website in under 5 minutes. Detailed installation instructions for different platforms. Complete API documentation and method references. Real-world implementation examples and use cases. ## What's Next? Our upcoming SaaS platform will provide real-time identity resolution, advanced funnel visualization, and AI-powered insights for predictive customer behavior. # Installation Source: https://dataglue.io/installation How to install and configure DataGlue on your website with different deployment options. # Installation DataGlue can be installed in multiple ways depending on your project setup and requirements. ## CDN Installation (Recommended) The easiest way to get started with DataGlue is to include it directly from our CDN: ```html theme={null} Your Website ``` ## NPM Installation For projects using a build system, install DataGlue via npm: ```bash theme={null} npm install dataglue ``` Then import and initialize: ```javascript theme={null} import DataGlue from 'dataglue'; // Initialize DataGlue const glue = new DataGlue(); ``` ## Configuration Options DataGlue can be configured through HTML attributes or JavaScript: ### HTML Configuration ```html theme={null} ``` ### JavaScript Configuration ```javascript theme={null} // Configure after loading window.addEventListener('DOMContentLoaded', function() { glue.configure({ modules: ['formSearch'], endpoint: 'https://api.example.com/data' }); }); ``` ## Verification After installation, verify DataGlue is working: 1. Open browser developer tools 2. Check for initialization message in console 3. Verify data collection in localStorage ```javascript theme={null} // Check if DataGlue is loaded console.log(window.glue ? 'DataGlue loaded' : 'DataGlue not found'); ``` ## Next Steps * [Quick Start Guide](/quickstart) - Get up and running in 5 minutes * [Form Integration](/features/form-integration) - Connect your forms * [Dynamic Content](/features/dynamic-content) - Personalize experiences # Journey Tracking Source: https://dataglue.io/journey-tracking Track complete user journeys from first visit to conversion # 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 ```html theme={null} ``` ### 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` ```json theme={null} { "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` ```json theme={null} [ { "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` ```json theme={null} [ { "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 ```javascript theme={null} // 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 ```javascript theme={null} // 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 ```javascript theme={null} // 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:** ```json theme={null} { "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):** ```javascript theme={null} 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:** ```json theme={null} { "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:** ```javascript theme={null} 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:** ```json theme={null} { "glue_user_id": "abc-123-uuid", "data": { "profile": {...}, "sessions": [...], "events": [...], "summary": {...} }, "synced_at": "2024-01-20T16:30:00Z" } ``` **Example Handler:** ```javascript theme={null} 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 ```html theme={null} ``` | Attribute | Default | Description | | ------------------ | ---------------------- | ------------------------------------------ | | `profile-endpoint` | - | Your API base URL | | `auto-identify` | `true` | Auto-identify from email fields/URL params | | `auto-sync` | `false` | Automatically sync data to server | | `track-events` | `pageview,form_submit` | Events 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 ```javascript theme={null} // 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 ```javascript theme={null} // 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 ```javascript theme={null} // 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 ```javascript theme={null} // 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 ```javascript theme={null} // Enable detailed logging localStorage.setItem('glue_dev_mode', 'true'); location.reload(); ``` ### Export Journey ```javascript theme={null} // 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. # Glue Attributes Reference Source: https://dataglue.io/reference/glue-attributes Complete reference guide for all DataGlue attributes, their purposes, storage mechanisms, and usage patterns. This comprehensive reference covers all attributes captured and managed by DataGlue, including their storage locations, purposes, and practical usage examples. ## Attribute Overview DataGlue captures and manages visitor attributes using a consistent `glue_` prefix for easy identification. All attributes are stored across multiple storage mechanisms for redundancy and accessibility. ```mermaid theme={null} graph TB subgraph "Data Sources" URL[URL Parameters] COOK[Browser Cookies] TT[TikTok SessionStorage] FB[Facebook Cookies] GEO[Geolocation API] UA[User Agent] end subgraph "DataGlue Core" INIT[Initialization] ATTR[Attribution Module] STOR[Storage Module] FORM[Form Integration] DYN[Dynamic Content] end subgraph "Storage Mechanisms" LS[localStorage] SS[sessionStorage] CK[Cookies] end URL --> INIT COOK --> ATTR TT --> ATTR FB --> ATTR GEO --> ATTR UA --> ATTR INIT --> ATTR ATTR --> STOR STOR --> LS STOR --> SS STOR --> CK ``` ### Storage Strategy **Persistent storage** - Survives browser restarts * User identification data * Attribution history * First-visit information * Geolocation data **Session-only storage** - Cleared when tab closes * Current session data * URL tracking * Browser information * Temporary values **Cross-tab persistent** - Available to server-side code * User ID with expiration * Attribution data with TTL * Third-party integration data ## User Identification Attributes ### `glue_user_id` Unique identifier for each visitor using UUID v7 format **Storage**: localStorage, cookies (365 days) **Auto-generated**: Yes, on first visit **Example**: `01234567-89ab-cdef-0123-456789abcdef` ```javascript theme={null} // Access user ID const userId = window.glue.storage.get('glue_user_id'); console.log('Visitor ID:', userId); ``` ### `glue_user_firstseen` ISO timestamp recording when the user first visited the site **Storage**: localStorage **Auto-generated**: Yes, on first visit only **Example**: `2024-01-15T10:30:00.000Z` ### `glue_session_start` ISO timestamp recording when the current session started **Storage**: sessionStorage **Auto-generated**: Yes, updates on each page load **Example**: `2024-01-15T14:20:00.000Z` ## Attribution Tracking Attributes DataGlue captures both initial and last-touch attribution for comprehensive tracking. **First vs Last Touch**: DataGlue preserves both the first touchpoint (how they originally found you) and the last touchpoint (what brought them back) for accurate attribution. ### UTM Parameters #### `glue_initial_utm_source` / `glue_last_utm_source` Tracks traffic source (google, facebook, newsletter, etc.) **Storage**: localStorage, cookies (365 days) **Examples**: `google`, `facebook`, `newsletter`, `linkedin` ```html theme={null} ``` #### `glue_initial_utm_medium` / `glue_last_utm_medium` Tracks marketing medium (cpc, email, social, etc.) **Storage**: localStorage, cookies (365 days) **Examples**: `cpc`, `email`, `social`, `organic` #### `glue_initial_utm_campaign` / `glue_last_utm_campaign` Tracks campaign name **Storage**: localStorage, cookies (365 days) **Examples**: `summer_sale`, `product_launch`, `retargeting_q1` #### `glue_initial_utm_term` / `glue_last_utm_term` Tracks search keywords or ad targeting **Storage**: localStorage, cookies (365 days) **Examples**: `data analytics`, `tracking software`, `crm integration` #### `glue_initial_utm_content` / `glue_last_utm_content` Tracks content variation or A/B test version **Storage**: localStorage, cookies (365 days) **Examples**: `header_cta`, `sidebar_ad`, `banner_v2` #### `glue_initial_utm_id` / `glue_last_utm_id` Unique campaign identifier for advanced tracking **Storage**: localStorage, cookies (365 days) **Examples**: `camp_12345`, `promo_summer_2024` ### Platform-Specific Attribution #### Facebook Click ID Facebook click identifier for ad attribution **Storage**: localStorage, cookies (365 days) **Example**: `IwAR0Kg4h2k3j5l6m7n8o9p0q1r2s3t4u5v6w7x8y9z` #### Google Click ID Google Ads click identifier for conversion tracking **Storage**: localStorage, cookies (365 days) **Example**: `CjwKCAjw_b-WBhAb123Example` #### TikTok Tracking ID TikTok tracking identifier for ad attribution **Storage**: localStorage, cookies (365 days) **Example**: `tt_abc123def456` ## Session Tracking Attributes ### URL Tracking #### `glue_url_current` Current page URL **Storage**: sessionStorage **Auto-generated**: Yes, updates on each page load **Example**: `https://example.com/product/details` #### `glue_url_last` Previous page URL (referrer) **Storage**: sessionStorage **Auto-generated**: Yes, if referrer exists **Example**: `https://google.com/search?q=example` #### `glue_url_first` First landing page URL on the domain **Storage**: localStorage **Auto-generated**: Yes, stored only once **Example**: `https://example.com/landing-page` #### `glue_user_referrer` Domain of the referring site **Storage**: localStorage **Auto-generated**: Yes, if referrer exists and not set **Example**: `google.com`, `facebook.com`, `linkedin.com` ### Browser Information #### `glue_user_browser` Browser detection results in JSON format **Storage**: sessionStorage **Auto-generated**: Yes **Example**: ```json theme={null} { "browser": "Chrome", "version": "120", "os": "macOS", "device": "desktop" } ``` ## Form Data Attributes ### Personal Information #### `glue_fname` First name from forms or URL parameters **Storage**: localStorage, sessionStorage, cookies (30 days) **Sources**: URL params (`?fname=John`), form inputs, manual API calls ```html theme={null} ``` #### `glue_lname` Last name from forms or URL parameters **Storage**: localStorage, sessionStorage, cookies (30 days) **Sources**: URL params (`?lname=Doe`), form inputs, manual API calls #### `glue_email` Email address from forms or URL parameters **Storage**: localStorage, sessionStorage, cookies (30 days) **Sources**: URL params (`?email=john@example.com`), form inputs, manual API calls ```html theme={null} ``` #### `glue_mobile` Phone number from forms or URL parameters **Storage**: localStorage, sessionStorage, cookies (30 days) **Sources**: URL params (`?mobile=+1234567890`), form inputs, manual API calls ```html theme={null} ``` ### Special Parameters #### Calendly Integration Calendly meeting or scheduling parameter **Storage**: localStorage, sessionStorage, cookies (30 days) **Example**: `scheduled`, `completed`, `cancelled` ```html theme={null} ``` ## Third-Party Integration Attributes ### TikTok Attribution DataGlue automatically captures TikTok attribution data from sessionStorage: #### `glue_tt_appInfo` TikTok application information **Source**: TikTok sessionStorage (`tt_appInfo`) **Storage**: localStorage, sessionStorage, cookies (30 days) #### `glue_tt_pixel_session_index` TikTok pixel session tracking index **Source**: TikTok sessionStorage (`tt_pixel_session_index`) **Storage**: localStorage, sessionStorage, cookies (30 days) #### `glue_tt_sessionId` TikTok session identifier **Source**: TikTok sessionStorage (`tt_sessionId`) **Storage**: localStorage, sessionStorage, cookies (30 days) ### Facebook Attribution DataGlue automatically captures Facebook attribution data from cookies: #### `glue_fb_fbp` Facebook browser pixel identifier **Source**: Facebook cookie (`_fbp`) **Storage**: localStorage, sessionStorage, cookies (30 days) **Example**: `fb.1.1640995200000.1234567890` #### `glue_fb_fbc` Facebook click identifier **Source**: Facebook cookie (`_fbc`) **Storage**: localStorage, sessionStorage, cookies (30 days) **Example**: `fb.1.1640995200000.AbCdEf123456` ## Geolocation Attributes ### `glue_user_geolocation` IP-based geolocation data in formatted string **Storage**: localStorage **Source**: geojs.io API (asynchronous) **Format**: `{country_code} | {country} | {city} | {latitude}, {longitude}` **Example**: `US | United States | New York | 40.7128, -74.0060` Geolocation data is fetched asynchronously and may not be immediately available on the first page load. ```javascript theme={null} // Check if geolocation data is available const geoData = window.glue.storage.get('glue_user_geolocation'); if (geoData) { const [countryCode, country, city, coords] = geoData.split(' | '); console.log('User location:', { countryCode, country, city, coords }); } ``` ## Element Selectors & Form Integration DataGlue uses sophisticated selector patterns to find and populate elements: ### Selector Patterns ```html theme={null} ``` ```html theme={null} ``` ```html theme={null}
Content
```
```html theme={null}
Content
```
```html theme={null}
```
### Fillout Form Integration For Fillout forms, DataGlue automatically adds data attributes: ```html theme={null}
``` #### Valid Prefixes for Fillout All attributes starting with `glue_` are automatically added: * `data-glue_user_id` * `data-glue_email` * `data-glue_fname` * etc. All UTM parameters are added with `data-utm_` prefix: * `data-utm_source` * `data-utm_campaign` * `data-utm_medium` * etc. All URL query parameters are added as data attributes: * `?custom_param=value` → `data-custom_param="value"` * `?lead_source=webinar` → `data-lead_source="webinar"` ## API Usage Examples ### Reading Attributes ```javascript Basic Access theme={null} // Get specific attribute const userEmail = window.glue.storage.get('glue_email'); const utmSource = window.glue.storage.get('glue_initial_utm_source'); // Check if attribute exists if (userEmail) { console.log('User email:', userEmail); } ``` ```javascript Visitor Context theme={null} // Get complete visitor context const context = window.glue.getVisitorContext(); console.log('Complete context:', context); // Get attribution data only const attribution = window.glue.getAttribution(); console.log('Attribution:', attribution); ``` ```javascript Multiple Attributes theme={null} // Get all glue attributes const allGlueData = Object.keys(localStorage) .filter(key => key.startsWith('glue_')) .reduce((acc, key) => { acc[key] = localStorage.getItem(key); return acc; }, {}); console.log('All DataGlue attributes:', allGlueData); ``` ### Setting Attributes ```javascript Basic Setting theme={null} // Set with localStorage only window.glue.storage.set('glue_custom_attr', 'value'); // Set with cookie expiration (30 days) window.glue.storage.set('glue_user_tier', 'premium', 30); ``` ```javascript Attribution Override theme={null} // Manual attribution setting window.glue.setAttribution({ utm_source: 'manual_override', utm_campaign: 'special_promotion' }); ``` ```javascript Bulk Setting theme={null} // Set multiple attributes const userData = { 'glue_company': 'Acme Corp', 'glue_title': 'Marketing Manager', 'glue_industry': 'Technology' }; Object.entries(userData).forEach(([key, value]) => { window.glue.storage.set(key, value, 30); }); ``` ### Clearing Attributes ```javascript Single Attribute theme={null} // Clear specific attribute window.glue.storage.remove('glue_custom_attr'); ``` ```javascript All Attributes theme={null} // Clear all DataGlue storage window.glue.storage.clear(); ``` ```javascript Selective Clearing theme={null} // Clear only UTM data Object.keys(localStorage) .filter(key => key.includes('utm_')) .forEach(key => localStorage.removeItem(key)); ``` ## Browser Compatibility | Feature | Chrome | Firefox | Safari | Edge | IE11 | | -------------- | ------ | ------- | ------ | ---- | ---- | | localStorage | ✅ | ✅ | ✅ | ✅ | ✅ | | sessionStorage | ✅ | ✅ | ✅ | ✅ | ✅ | | Cookies | ✅ | ✅ | ✅ | ✅ | ✅ | | Feature | Chrome | Firefox | Safari | Edge | IE11 | | ---------------- | ------ | ------- | ------ | ---- | ---- | | URL SearchParams | ✅ | ✅ | ✅ | ✅ | ❌\* | | UUID v7 | ✅ | ✅ | ✅ | ✅ | ❌\* | | Geolocation API | ✅ | ✅ | ✅ | ✅ | ✅ | | CSS.escape() | ✅ | ✅ | ✅ | ✅ | ❌\* | \*Requires polyfill for IE11 support ## Best Practices **Naming Convention**: Use descriptive names for custom attributes and maintain the `glue_` prefix for consistency. **Data Privacy**: Ensure compliance with privacy regulations when collecting personal information. DataGlue respects `doNotTrack` browser settings. ### Recommended Patterns 1. **Consistent Prefixing**: Always use `glue_` prefix for custom attributes 2. **Descriptive Names**: Use clear, meaningful names like `glue_lead_source` instead of `glue_ls` 3. **Appropriate Expiration**: Set reasonable cookie expiration times based on data sensitivity 4. **Graceful Fallbacks**: Always check if data exists before using it ### Security Considerations * DataGlue automatically sanitizes stored values to prevent XSS * No sensitive data is stored without explicit configuration * All data is stored client-side unless explicitly sent to servers * Uses CSS.escape() to safely handle special characters in selectors # Server Implementation Source: https://dataglue.io/server-implementation Complete guide to implementing DataGlue server-side endpoints # Server Implementation Guide This guide shows you exactly how to build the server-side API to receive and store DataGlue journey data. ## Database Setup You need **2 collections/tables**: ### MongoDB Collections ```javascript theme={null} // Collection: users { glue_user_id: String (indexed, unique), email: String (indexed), traits: Object, attribution: { first_touch: Object, last_touch: Object }, first_seen: Date, last_seen: Date, identified: Boolean, identified_at: Date } // Collection: events { event_id: String (indexed, unique), glue_user_id: String (indexed), session_id: String (indexed), event: String (indexed), timestamp: Date (indexed), properties: Object, email: String (indexed, optional) } ``` ### PostgreSQL Tables ```sql theme={null} -- Table: users CREATE TABLE users ( id SERIAL PRIMARY KEY, glue_user_id VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255), traits JSONB, attribution JSONB, first_seen TIMESTAMP, last_seen TIMESTAMP, identified BOOLEAN DEFAULT FALSE, identified_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_users_glue_user_id ON users(glue_user_id); CREATE INDEX idx_users_email ON users(email); -- Table: events CREATE TABLE events ( id SERIAL PRIMARY KEY, event_id VARCHAR(255) UNIQUE NOT NULL, glue_user_id VARCHAR(255) NOT NULL, session_id VARCHAR(255), event VARCHAR(100) NOT NULL, timestamp TIMESTAMP NOT NULL, properties JSONB, email VARCHAR(255), created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_events_glue_user_id ON events(glue_user_id); CREATE INDEX idx_events_session_id ON events(session_id); CREATE INDEX idx_events_event ON events(event); CREATE INDEX idx_events_timestamp ON events(timestamp); CREATE INDEX idx_events_email ON events(email); ``` *** ## Complete Server Implementation ### Node.js + Express + MongoDB ```javascript theme={null} const express = require('express'); const { MongoClient } = require('mongodb'); const app = express(); app.use(express.json()); // MongoDB connection const client = new MongoClient('mongodb://localhost:27017'); const db = client.db('dataglue'); const users = db.collection('users'); const events = db.collection('events'); // Create indexes async function createIndexes() { await users.createIndex({ glue_user_id: 1 }, { unique: true }); await users.createIndex({ email: 1 }); await events.createIndex({ event_id: 1 }, { unique: true }); await events.createIndex({ glue_user_id: 1 }); await events.createIndex({ session_id: 1 }); await events.createIndex({ event: 1 }); await events.createIndex({ timestamp: -1 }); } /** * POST /glue/identify * Links glue_user_id to email address */ app.post('/glue/identify', async (req, res) => { try { const { glue_user_id, email, traits, context } = req.body; // Validate required fields if (!glue_user_id || !email) { return res.status(400).json({ success: false, error: 'glue_user_id and email are required' }); } // Upsert user profile const result = await users.updateOne( { glue_user_id }, { $set: { email, traits: traits || {}, last_seen: new Date(), identified: true, identified_at: new Date(), 'attribution.last_touch': { utm_source: context?.utm_source, utm_medium: context?.utm_medium, utm_campaign: context?.utm_campaign, referrer: context?.referrer, landing_page: context?.page?.url, timestamp: new Date() } }, $setOnInsert: { glue_user_id, first_seen: new Date(), 'attribution.first_touch': { utm_source: context?.utm_source, utm_medium: context?.utm_medium, utm_campaign: context?.utm_campaign, referrer: context?.referrer, landing_page: context?.page?.url, timestamp: new Date() } } }, { upsert: true } ); console.log(`User identified: ${email} (${glue_user_id})`); res.json({ success: true, user_id: glue_user_id, new_user: result.upsertedCount > 0 }); } catch (error) { console.error('Identify error:', error); res.status(500).json({ success: false, error: error.message }); } }); /** * POST /glue/track * Records individual events */ app.post('/glue/track', async (req, res) => { try { const { glue_user_id, session_id, email, event, properties, timestamp, context } = req.body; // Validate required fields if (!glue_user_id || !event) { return res.status(400).json({ success: false, error: 'glue_user_id and event are required' }); } // Insert event const eventDoc = { event_id: `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, glue_user_id, session_id, email, event, properties: properties || {}, timestamp: new Date(timestamp || Date.now()), context: context || {}, created_at: new Date() }; await events.insertOne(eventDoc); // Update user last_seen await users.updateOne( { glue_user_id }, { $set: { last_seen: new Date() }, $inc: { total_events: 1 } } ); console.log(`Event tracked: ${event} for ${glue_user_id}`); res.json({ success: true, event_id: eventDoc.event_id }); } catch (error) { console.error('Track error:', error); res.status(500).json({ success: false, error: error.message }); } }); /** * POST /glue/sync * Receives complete user data (bulk sync) */ app.post('/glue/sync', async (req, res) => { try { const { glue_user_id, data, synced_at } = req.body; if (!glue_user_id || !data) { return res.status(400).json({ success: false, error: 'glue_user_id and data are required' }); } // Update user profile await users.updateOne( { glue_user_id }, { $set: { ...data.profile, last_synced: new Date(synced_at), total_sessions: data.sessions?.length || 0, total_events: data.events?.length || 0 } }, { upsert: true } ); // Bulk insert events (ignore duplicates) if (data.events && data.events.length > 0) { try { await events.insertMany(data.events, { ordered: false }); } catch (error) { // Ignore duplicate key errors (events already synced) if (error.code !== 11000) { throw error; } } } console.log(`Data synced for ${glue_user_id}: ${data.events?.length || 0} events`); res.json({ success: true, events_synced: data.events?.length || 0, sessions_synced: data.sessions?.length || 0 }); } catch (error) { console.error('Sync error:', error); res.status(500).json({ success: false, error: error.message }); } }); /** * GET /glue/user/:glue_user_id * Retrieve complete user journey */ app.get('/glue/user/:glue_user_id', async (req, res) => { try { const { glue_user_id } = req.params; // Get user profile const user = await users.findOne({ glue_user_id }); if (!user) { return res.status(404).json({ success: false, error: 'User not found' }); } // Get all events for this user const userEvents = await events .find({ glue_user_id }) .sort({ timestamp: 1 }) .toArray(); res.json({ success: true, user, events: userEvents, summary: { total_events: userEvents.length, first_event: userEvents[0]?.timestamp, last_event: userEvents[userEvents.length - 1]?.timestamp } }); } catch (error) { console.error('Get user error:', error); res.status(500).json({ success: false, error: error.message }); } }); /** * GET /glue/user/email/:email * Retrieve user by email */ app.get('/glue/user/email/:email', async (req, res) => { try { const { email } = req.params; const user = await users.findOne({ email }); if (!user) { return res.status(404).json({ success: false, error: 'User not found' }); } // Get all events const userEvents = await events .find({ glue_user_id: user.glue_user_id }) .sort({ timestamp: 1 }) .toArray(); res.json({ success: true, user, events: userEvents }); } catch (error) { console.error('Get user by email error:', error); res.status(500).json({ success: false, error: error.message }); } }); // Start server const PORT = process.env.PORT || 3000; app.listen(PORT, async () => { await createIndexes(); console.log(`DataGlue API server running on port ${PORT}`); }); ``` *** ## Deploy to Production ### Option 1: Vercel/Netlify Functions Create `api/glue/identify.js`: ```javascript theme={null} const { MongoClient } = require('mongodb'); let cachedDb = null; async function connectToDatabase() { if (cachedDb) return cachedDb; const client = await MongoClient.connect(process.env.MONGODB_URI); cachedDb = client.db('dataglue'); return cachedDb; } module.exports = async (req, res) => { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } const db = await connectToDatabase(); const { glue_user_id, email, traits, context } = req.body; await db.collection('users').updateOne( { glue_user_id }, { $set: { email, traits, identified: true, last_seen: new Date() } }, { upsert: true } ); res.json({ success: true }); }; ``` ### Option 2: Railway/Render ```bash theme={null} # Deploy Express app directly npm install express mongodb node server.js ``` ### Option 3: Cloudflare Workers ```javascript theme={null} export default { async fetch(request, env) { if (request.method !== 'POST') { return new Response('Method not allowed', { status: 405 }); } const data = await request.json(); // Store in D1 (Cloudflare's database) await env.DB.prepare( 'INSERT INTO users (glue_user_id, email, traits) VALUES (?, ?, ?)' ).bind(data.glue_user_id, data.email, JSON.stringify(data.traits)).run(); return Response.json({ success: true }); } }; ``` *** ## Environment Variables ```bash theme={null} # .env file MONGODB_URI=mongodb://localhost:27017/dataglue PORT=3000 # Production (Vercel/Netlify) MONGODB_URI=mongodb+srv://user:pass@cluster.mongodb.net/dataglue ``` *** ## Testing Your Server ### Test Identify Endpoint ```bash theme={null} curl -X POST http://localhost:3000/glue/identify \ -H "Content-Type: application/json" \ -d '{ "glue_user_id": "test-123", "email": "test@example.com", "traits": { "fname": "Test", "lname": "User" }, "context": { "utm_source": "google", "session_id": "session-456" } }' ``` ### Test Track Endpoint ```bash theme={null} curl -X POST http://localhost:3000/glue/track \ -H "Content-Type: application/json" \ -d '{ "glue_user_id": "test-123", "session_id": "session-456", "event": "page_view", "properties": { "url": "/landing", "title": "Landing Page" }, "timestamp": 1704376800000 }' ``` ### Test Get User ```bash theme={null} curl http://localhost:3000/glue/user/test-123 ``` *** ## Query Examples ### Get User Journey ```javascript theme={null} // Get complete user journey const user = await users.findOne({ email: 'user@example.com' }); const journey = await events .find({ glue_user_id: user.glue_user_id }) .sort({ timestamp: 1 }) .toArray(); console.log(`${user.email} journey:`, journey); ``` ### Conversion Funnel ```javascript theme={null} const funnel = await events.aggregate([ { $match: { event: { $in: ['page_view', 'form_submit', 'purchase'] } } }, { $group: { _id: '$event', count: { $sum: 1 } } } ]).toArray(); ``` ### Attribution Report ```javascript theme={null} const sources = await users.aggregate([ { $group: { _id: '$attribution.first_touch.utm_source', count: { $sum: 1 }, conversions: { $sum: { $cond: ['$identified', 1, 0] } } } }, { $sort: { count: -1 } } ]).toArray(); ``` *** ## Security Best Practices 1. **Validate requests** ```javascript theme={null} if (!glue_user_id || !email) { return res.status(400).json({ error: 'Invalid request' }); } ``` 2. **Rate limiting** ```javascript theme={null} const rateLimit = require('express-rate-limit'); app.use('/glue', rateLimit({ windowMs: 60 * 1000, // 1 minute max: 100 // 100 requests per minute })); ``` 3. **CORS configuration** ```javascript theme={null} app.use(cors({ origin: ['https://yoursite.com'], methods: ['POST'] })); ``` 4. **API key authentication (optional)** ```javascript theme={null} app.use('/glue', (req, res, next) => { const apiKey = req.headers['x-api-key']; if (apiKey !== process.env.API_KEY) { return res.status(401).json({ error: 'Unauthorized' }); } next(); }); ``` *** ## Next Steps 1. ✅ Deploy server to production 2. ✅ Add `profile-endpoint` to your DataGlue script tag 3. ✅ Test with a real form submission 4. ✅ Query your database to see user journeys 5. ✅ Build analytics dashboards on top of this data # Server-Side Setup Guide Source: https://dataglue.io/server-setup Complete guide to setting up a backend for DataGlue journey tracking # Server-Side Setup Guide This guide shows you how to set up a backend to collect and manage DataGlue attribution and journey data. Choose the approach that best fits your stack. ## Architecture Overview ```mermaid theme={null} graph TB subgraph "User's Browser" A[User visits site] --> B[DataGlue tracks] B --> C[localStorage: sessions, events, profile] C --> D[User fills form] D --> E[Form submits with journey data] end subgraph "Server Options" E --> F{Choose Backend} F -->|Option 1| G[N8N Webhooks] F -->|Option 2| H[Serverless Functions] F -->|Option 3| I[Custom Server] end subgraph "Database" G --> J[(Supabase/PostgreSQL)] H --> J I --> J J --> K[users table] J --> L[events table] end subgraph "Query & Analytics" K --> M[User Journeys] L --> M M --> N[Analytics Dashboard] M --> O[Attribution Reports] M --> P[Conversion Funnels] end ``` ## Data Flow Diagram ```mermaid theme={null} sequenceDiagram participant Browser participant DataGlue participant Form participant Server participant Database Browser->>DataGlue: Page load DataGlue->>DataGlue: Generate glue_user_id DataGlue->>DataGlue: Store in localStorage Note over DataGlue: Tracks sessions & events Browser->>Form: User fills email DataGlue->>DataGlue: Detects email DataGlue->>DataGlue: Updates profile Browser->>Form: User submits DataGlue->>Form: Inject hidden fields Note over Form: glue_user_id, session_id,
total_sessions, attribution, etc. Form->>Server: POST with journey data Server->>Database: Check if email exists alt User Exists (Cross-Device) Database->>Server: User found Server->>Database: Merge glue_user_ids Server->>Database: Link devices else New User Database->>Server: Not found Server->>Database: Create new user end Server->>Database: Insert events Server->>Browser: Success response ``` *** ## Option 1: N8N Webhooks + Supabase (Easiest) **Best for:** Non-developers, rapid prototyping, visual workflows ### Architecture ```mermaid theme={null} graph LR A[DataGlue] -->|POST /identify| B[N8N Webhook] A -->|POST /track| C[N8N Webhook] A -->|POST /sync| D[N8N Webhook] B --> E{User Exists?} E -->|Yes| F[Merge Devices] E -->|No| G[Create User] F --> H[(Supabase)] G --> H C --> H D --> H H --> I[users table] H --> J[events table] ``` ### Setup Steps #### 1. Create Supabase Project ```sql theme={null} -- Create users table CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), glue_user_id TEXT UNIQUE NOT NULL, email TEXT, traits JSONB DEFAULT '{}'::jsonb, linked_ids TEXT[] DEFAULT ARRAY[]::TEXT[], first_seen TIMESTAMPTZ DEFAULT NOW(), last_seen TIMESTAMPTZ DEFAULT NOW(), first_touch_source TEXT, first_touch_medium TEXT, first_touch_campaign TEXT, last_touch_source TEXT, last_touch_medium TEXT, last_touch_campaign TEXT, total_sessions INTEGER DEFAULT 0, total_events INTEGER DEFAULT 0, identified BOOLEAN DEFAULT FALSE, identified_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_users_glue_user_id ON users(glue_user_id); -- Create events table CREATE TABLE events ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), event_id TEXT UNIQUE NOT NULL, glue_user_id TEXT NOT NULL, session_id TEXT, event TEXT NOT NULL, properties JSONB DEFAULT '{}'::jsonb, timestamp TIMESTAMPTZ NOT NULL, email TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_events_glue_user_id ON events(glue_user_id); CREATE INDEX idx_events_session_id ON events(session_id); CREATE INDEX idx_events_event ON events(event); CREATE INDEX idx_events_timestamp ON events(timestamp DESC); ``` #### 2. Create N8N Workflows **Workflow 1: Identify User** ``` Webhook (POST /webhook/glue/identify) ↓ Supabase: UPSERT users SET email, traits, last_seen, identified = true ON CONFLICT (glue_user_id) DO UPDATE ↓ Check for Email Duplicates SELECT * FROM users WHERE email = {{ email }} AND glue_user_id != {{ glue_user_id }} ↓ IF duplicates found: Supabase: UPDATE users SET linked_ids = array_append(linked_ids, {{ new_glue_user_id }}) ↓ Respond: { success: true } ``` **Workflow 2: Track Event** ``` Webhook (POST /webhook/glue/track) ↓ Supabase: INSERT INTO events VALUES (event_id, glue_user_id, event, properties, timestamp) ↓ Supabase: UPDATE users SET last_seen = NOW(), total_events = total_events + 1 ↓ Respond: { success: true } ``` **Workflow 3: Sync Complete Journey** ``` Webhook (POST /webhook/glue/sync) ↓ Parse journey_data JSON ↓ Loop through events array ↓ Supabase: INSERT INTO events (ON CONFLICT DO NOTHING) ↓ Supabase: UPSERT user profile ↓ Respond: { success: true, events_synced: X } ``` #### 3. Configure DataGlue ```html theme={null} ``` ### Pros & Cons **Pros:** * ✅ No code required (visual workflows) * ✅ Built-in error handling & retries * ✅ Easy debugging (see every request) * ✅ Can add multiple outputs (Slack, email, etc.) * ✅ Supabase has generous free tier **Cons:** * ❌ N8N costs \$20/month (or self-host) * ❌ Slightly slower than custom server * ❌ Limited to N8N's capabilities *** ## Option 2: Serverless Functions (Vercel/Netlify) **Best for:** Next.js/React apps, developers who want simplicity ### Architecture ```mermaid theme={null} graph LR A[DataGlue] -->|POST| B[API Route] B --> C{Function Type} C -->|/api/identify| D[Identify Function] C -->|/api/track| E[Track Function] C -->|/api/sync| F[Sync Function] D --> G[(Supabase)] E --> G F --> G G --> H[PostgreSQL] ``` ### Setup Steps #### 1. Install Dependencies ```bash theme={null} npm install @supabase/supabase-js ``` #### 2. Create API Routes **File: `pages/api/glue/identify.js` (Next.js)** ```javascript theme={null} import { createClient } from '@supabase/supabase-js' const supabase = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_KEY ) export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }) } const { glue_user_id, email, traits, context } = req.body if (!glue_user_id || !email) { return res.status(400).json({ error: 'Missing required fields' }) } try { // Check if user with this email already exists const { data: existingUser } = await supabase .from('users') .select('*') .eq('email', email) .single() if (existingUser && existingUser.glue_user_id !== glue_user_id) { // Cross-device: merge the IDs await supabase .from('users') .update({ linked_ids: [...(existingUser.linked_ids || []), glue_user_id] }) .eq('glue_user_id', existingUser.glue_user_id) // Update all events to point to primary ID await supabase .from('events') .update({ glue_user_id: existingUser.glue_user_id }) .eq('glue_user_id', glue_user_id) } else { // Upsert user await supabase .from('users') .upsert({ glue_user_id, email, traits, identified: true, identified_at: new Date().toISOString(), last_seen: new Date().toISOString(), last_touch_source: context?.utm_source, last_touch_medium: context?.utm_medium, last_touch_campaign: context?.utm_campaign, }, { onConflict: 'glue_user_id' }) } res.json({ success: true, glue_user_id }) } catch (error) { console.error('Identify error:', error) res.status(500).json({ error: error.message }) } } ``` **File: `pages/api/glue/track.js`** ```javascript theme={null} import { createClient } from '@supabase/supabase-js' const supabase = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_KEY ) export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }) } const { glue_user_id, session_id, event, properties, timestamp } = req.body try { // Insert event await supabase .from('events') .insert({ event_id: `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, glue_user_id, session_id, event, properties, timestamp: new Date(timestamp) }) // Update user stats await supabase .from('users') .update({ last_seen: new Date().toISOString(), total_events: supabase.raw('total_events + 1') }) .eq('glue_user_id', glue_user_id) res.json({ success: true }) } catch (error) { console.error('Track error:', error) res.status(500).json({ error: error.message }) } } ``` #### 3. Configure DataGlue ```html theme={null} ``` ### Pros & Cons **Pros:** * ✅ No server management * ✅ Auto-scaling * ✅ Generous free tiers (Vercel: 100GB bandwidth) * ✅ Fast cold starts * ✅ Easy to deploy **Cons:** * ❌ Requires coding * ❌ Limited execution time (10-60 seconds) * ❌ Cold starts can add latency *** ## Option 3: Custom Server (Node.js + Express) **Best for:** Developers who want full control, high-volume apps ### Architecture ```mermaid theme={null} graph TB A[DataGlue] -->|HTTPS| B[Load Balancer] B --> C[Express Server] C --> D{Route} D -->|POST /identify| E[Identify Handler] D -->|POST /track| F[Track Handler] D -->|POST /sync| G[Sync Handler] E --> H[Business Logic] F --> H G --> H H --> I[(PostgreSQL)] H --> J[(Redis Cache)] I --> K[users] I --> L[events] ``` ### Setup Steps #### 1. Install Dependencies ```bash theme={null} npm install express pg redis cors dotenv ``` #### 2. Create Server **File: `server.js`** ```javascript theme={null} const express = require('express') const { Pool } = require('pg') const cors = require('cors') const app = express() app.use(express.json()) app.use(cors()) // PostgreSQL connection const pool = new Pool({ connectionString: process.env.DATABASE_URL }) // POST /glue/identify app.post('/glue/identify', async (req, res) => { const { glue_user_id, email, traits, context } = req.body if (!glue_user_id || !email) { return res.status(400).json({ error: 'Missing required fields' }) } try { // Check for existing user with this email const existing = await pool.query( 'SELECT * FROM users WHERE email = $1 AND glue_user_id != $2', [email, glue_user_id] ) if (existing.rows.length > 0) { // Cross-device merge const primaryUser = existing.rows[0] // Add to linked_ids await pool.query( 'UPDATE users SET linked_ids = array_append(linked_ids, $1) WHERE glue_user_id = $2', [glue_user_id, primaryUser.glue_user_id] ) // Merge events await pool.query( 'UPDATE events SET glue_user_id = $1 WHERE glue_user_id = $2', [primaryUser.glue_user_id, glue_user_id] ) } else { // Upsert user await pool.query(` INSERT INTO users ( glue_user_id, email, traits, identified, identified_at, last_seen, last_touch_source, last_touch_medium ) VALUES ($1, $2, $3, true, NOW(), NOW(), $4, $5) ON CONFLICT (glue_user_id) DO UPDATE SET email = $2, traits = $3, identified = true, identified_at = COALESCE(users.identified_at, NOW()), last_seen = NOW(), last_touch_source = $4, last_touch_medium = $5 `, [ glue_user_id, email, JSON.stringify(traits), context?.utm_source, context?.utm_medium ]) } res.json({ success: true, glue_user_id }) } catch (error) { console.error('Identify error:', error) res.status(500).json({ error: error.message }) } }) // POST /glue/track app.post('/glue/track', async (req, res) => { const { glue_user_id, session_id, event, properties, timestamp } = req.body try { // Insert event await pool.query(` INSERT INTO events (event_id, glue_user_id, session_id, event, properties, timestamp) VALUES ($1, $2, $3, $4, $5, $6) `, [ `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, glue_user_id, session_id, event, JSON.stringify(properties), new Date(timestamp) ]) // Update user await pool.query( 'UPDATE users SET last_seen = NOW(), total_events = total_events + 1 WHERE glue_user_id = $1', [glue_user_id] ) res.json({ success: true }) } catch (error) { console.error('Track error:', error) res.status(500).json({ error: error.message }) } }) // POST /glue/sync app.post('/glue/sync', async (req, res) => { const { glue_user_id, data } = req.body try { // Upsert user await pool.query(` INSERT INTO users (glue_user_id, email, traits, first_seen, last_seen) VALUES ($1, $2, $3, $4, NOW()) ON CONFLICT (glue_user_id) DO UPDATE SET email = COALESCE(users.email, $2), traits = $3, last_seen = NOW() `, [ glue_user_id, data.profile.email, JSON.stringify(data.profile.traits), data.profile.first_seen ]) // Bulk insert events for (const event of data.events) { await pool.query(` INSERT INTO events (event_id, glue_user_id, session_id, event, properties, timestamp) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (event_id) DO NOTHING `, [ event.event_id, glue_user_id, event.session_id, event.event, JSON.stringify(event.properties), event.timestamp ]) } res.json({ success: true, events_synced: data.events.length }) } catch (error) { console.error('Sync error:', error) res.status(500).json({ error: error.message }) } }) const PORT = process.env.PORT || 3000 app.listen(PORT, () => { console.log(`DataGlue server running on port ${PORT}`) }) ``` #### 3. Deploy ```bash theme={null} # Railway railway up # Or Render render deploy # Or Docker docker build -t dataglue-server . docker run -p 3000:3000 dataglue-server ``` ### Pros & Cons **Pros:** * ✅ Full control over logic * ✅ Can handle high volumes * ✅ No cold starts * ✅ Custom optimizations * ✅ Direct database access **Cons:** * ❌ Requires server management * ❌ Need to handle scaling * ❌ More code to maintain * ❌ Security considerations *** ## Cross-Device Identity Resolution ### The Challenge ```mermaid theme={null} graph TB A[iPhone Safari] -->|glue_user_id: ABC-123| B[localStorage] C[Laptop Chrome] -->|glue_user_id: XYZ-789| D[localStorage] E[Android Firefox] -->|glue_user_id: DEF-456| F[localStorage] B --> G[Session 1, 2, 3] D --> H[Session 4, 5] F --> I[Session 6] G --> J{Same Person?} H --> J I --> J J -->|Email: john@example.com| K[SERVER MERGES] K --> L[Primary ID: XYZ-789] L --> M[Linked IDs: ABC-123, DEF-456] ``` ### Solution: Email-Based Merging ```sql theme={null} -- When user identifies on any device -- Step 1: Check if email exists SELECT * FROM users WHERE email = 'john@example.com'; -- Step 2a: If exists, merge devices UPDATE users SET linked_ids = array_append(linked_ids, 'NEW-DEVICE-ID') WHERE email = 'john@example.com'; -- Step 2b: Unify events under primary ID UPDATE events SET glue_user_id = 'PRIMARY-ID' WHERE glue_user_id = 'NEW-DEVICE-ID'; -- Step 3: Query complete journey SELECT * FROM events WHERE glue_user_id IN ( SELECT glue_user_id FROM users WHERE email = 'john@example.com' UNION SELECT unnest(linked_ids) FROM users WHERE email = 'john@example.com' ) ORDER BY timestamp ASC; ``` *** ## Analytics Queries ### User Journey ```sql theme={null} -- Get complete user journey WITH user_profile AS ( SELECT * FROM users WHERE email = 'user@example.com' ), all_ids AS ( SELECT glue_user_id FROM user_profile UNION SELECT unnest(linked_ids) FROM user_profile ) SELECT e.timestamp, e.event, e.properties->>'url' as page, e.session_id FROM events e WHERE e.glue_user_id IN (SELECT * FROM all_ids) ORDER BY e.timestamp ASC; ``` ### Attribution Report ```sql theme={null} -- Users by first touch source SELECT first_touch_source, COUNT(*) as users, COUNT(*) FILTER (WHERE identified = true) as converted FROM users GROUP BY first_touch_source ORDER BY users DESC; ``` ### Conversion Funnel ```sql theme={null} -- Funnel analysis WITH funnel AS ( SELECT COUNT(DISTINCT CASE WHEN event = 'page_view' AND properties->>'path' = '/landing' THEN glue_user_id END) as landed, COUNT(DISTINCT CASE WHEN event = 'form_submit' THEN glue_user_id END) as submitted, COUNT(DISTINCT CASE WHEN event = 'page_view' AND properties->>'path' = '/thank-you' THEN glue_user_id END) as converted FROM events ) SELECT landed, submitted, converted, ROUND(100.0 * submitted / landed, 2) as submit_rate, ROUND(100.0 * converted / submitted, 2) as conversion_rate FROM funnel; ``` *** ## Comparison Table | Feature | N8N + Supabase | Serverless | Custom Server | | ------------------- | -------------- | ----------- | ------------- | | **Setup Time** | 1 hour | 2-3 hours | 4-8 hours | | **Coding Required** | None | Moderate | High | | **Cost (monthly)** | $0-$45 | $0-$20 | $10-$100 | | **Scalability** | Good | Excellent | Excellent | | **Performance** | Good | Good | Best | | **Debugging** | Visual | Logs | Full control | | **Maintenance** | Low | Medium | High | | **Best For** | Quick start | Modern apps | High volume | *** ## Recommended Approach ```mermaid theme={null} graph TD A[Start] --> B{What's your priority?} B -->|Speed to market| C[N8N + Supabase] B -->|Modern stack| D[Serverless Functions] B -->|Full control| E[Custom Server] C --> F[Perfect for:
- Non-developers
- MVP/Testing
- Visual workflows] D --> G[Perfect for:
- Next.js/React apps
- Moderate traffic
- Developer-friendly] E --> H[Perfect for:
- High volume
- Custom logic
- Enterprise apps] F --> I[Deploy in 1 hour] G --> I[Deploy in 3 hours] H --> I[Deploy in 1 day] ``` **Start simple, scale later:** 1. Begin with N8N + Supabase (fastest) 2. As you grow, migrate to Serverless Functions 3. At scale, move to Custom Server 4. Your data stays in Supabase/PostgreSQL throughout *** ## Next Steps 1. **Choose your approach** based on the comparison table 2. **Create database tables** in Supabase/PostgreSQL 3. **Set up your webhook/API endpoints** 4. **Configure DataGlue** with your endpoint URL 5. **Test with a form submission** 6. **Query your data** to see user journeys 7. **Build analytics dashboards** on top of the data Need help? Check out: * [Journey Tracking Guide](/docs/journey-tracking) * [Form Injection Guide](/docs/form-injection) * [Server Implementation Examples](/docs/server-implementation)