# Lead Capture System Architecture

**Last Updated:** 2026-03-28

## Overview

The lead capture system is a two-step progressive form popup that collects lead information and syncs it to both HubSpot CRM and Google Sheets. The system consists of five main components working together to provide a seamless lead capture experience.

## System Components

### 1. Popup Component (`v2/components/lead-capture-popup.php`)

**Purpose:** User-facing popup interface with two-step form

**Structure:**
- **Step 1:** Name and phone (required)
- **Step 2:** Email, notes, call preference (optional)
- **Success State:** Confirmation message

**Key Features:**
- Inline CSS and JavaScript (1636 lines total)
- Copy detection via `lead-capture-copy-detector.php`
- UTM tracking integration
- Session/localStorage management
- Responsive design (mobile-first)
- Accessibility features (ARIA labels, keyboard navigation)

**JavaScript Class:** `LeadCapturePopup`
- Handles form submission
- Manages step transitions
- Validates input (phone format, email format)
- Tracks UTM data and lead source
- Manages session state

### 2. API Endpoint (`v2/api/lead-capture.php`)

**Purpose:** Backend processing for form submissions

**Flow:**
1. **Step 1:** Creates lead in HubSpot (with temporary email) → Creates row in Google Sheets
2. **Step 2:** Updates HubSpot contact (replaces temp email with real email) → Updates Google Sheets row

**Key Features:**
- HubSpot Forms API v3 (preferred) with CRM API fallback
- Google Sheets append/update operations
- Phone normalization (E.164 format)
- Duplicate contact detection
- Error handling with retry logic
- Comprehensive logging

**HubSpot Integration:**
- **Step 1:** Uses temporary email `lead-{leadId}@temp.ordio.com` via Forms API v3
- **Step 2:** Finds contact by temp email/phone → Updates via CRM API → Submits to Forms API v3 for activity tracking

**Google Sheets Integration:**
- Spreadsheet ID: `1gHzf0CcCACPPLo3Xb4aBcO9cguraVa7eeLyYZvqCBAk`
- JWT authentication via service account
- Columns: Timestamp, Name, Phone, Email, Notes, Call Preference, Status, Source Page, Lead Source, Lead ID, Trigger Type, Page Label, Page Context

### 3. Trigger System (`v2/js/lead-capture-triggers.js`)

**Purpose:** Controls when popup appears

**Blocking overlays:** `v2/js/ordio-lead-capture-blocking-registry.js` (included from `head.php`) coordinates with demo/mobile-nav Alpine store, template download modals, tools export modals, and enterprise/pricing modals so automatic triggers pause while another modal is open. See [TRIGGER_CONFIGURATION.md](./TRIGGER_CONFIGURATION.md#blocking-overlays-automatic-triggers).

**Trigger Types:**
- **Time-based:** Varies by page priority (30s high/medium, 60s low)
- **Scroll-based:** Threshold varies (25% comparison, 35% blog, 40% standard)
- **Exit-intent:** Desktop (mouse leave) + Mobile (rapid scroll up)
- **Manual:** `window.leadCapturePopup.show('manual-callback')`

**Page Priority System:**
- **High:** Homepage, pricing, comparison, product pages (30s delay)
- **Medium:** Industry, templates, downloads (30s delay)
- **Low:** Tools pages (60s delay)

**Page Exclusions:** `/einladung` (Cello referral), `/kostenlos-testen`, and all `/tools` paths (`isToolsPath()`, `/^\/tools(\/|$)/`) – no automatic triggers; manual CTA only. The `low` priority tier still classifies `/tools` in `getPagePriority()` for consistency, but `setupTriggers()` returns before scheduling standard triggers on those URLs.

**Blog Optimization:**
- Longer time delay (30s)
- Lower scroll threshold (35%)
- Gentler exit intent (15s minimum, 25% scroll)

**Session Management:**
- Prevents multiple shows per page
- Tracks submission state (30-day expiration)
- Handles page refresh detection

### 4. Copy Management (`v2/components/lead-capture-copy-detector.php` + `v2/data/lead_capture_copy.php`)

**Purpose:** Dynamic copy based on page type

**Detection Method:**
1. Quick lookup table (O(1) for common pages)
2. Pattern matching (URL/filename contains pattern)
3. Default fallback

**Funnel Stages:**
- **TOF (Top of Funnel):** Tools pages, blog posts
- **MOF (Middle of Funnel):** Industry pages, templates
- **BOF (Bottom of Funnel):** Pricing, comparison pages

**Copy Structure:**
- Headline (H2)
- Description (subtitle)
- Funnel stage classification

### 5. Page Integration (100+ pages)

**Required Includes:**
```php
<?php include '../components/lead-capture-popup.php'; ?>
<script src="/v2/js/lead-capture-triggers.js"></script>
```

**Manual Triggers:**
```javascript
window.leadCapturePopup.show('manual-callback');
```

## Data Flow

### Step 1 Flow

```
User fills form → Frontend validation → POST /v2/api/lead-capture.php
    ↓
Backend validates (name, phone) → Normalize phone → Generate lead ID
    ↓
HubSpot: Create contact with temp email (Forms API v3)
    ↓
Google Sheets: Append row (timestamp, name, phone, lead ID, etc.)
    ↓
Return success + lead ID → Frontend shows Step 2
```

### Step 2 Flow

```
User fills Step 2 → Frontend validation → POST /v2/api/lead-capture.php
    ↓
Backend validates (lead ID required) → Get original data from Sheets
    ↓
Google Sheets: Update row (email, notes, call preference)
    ↓
HubSpot: Find contact by temp email first, then phone (prefer temp email) → Update via CRM API
    ↓
Auto-merge: If 2 contacts with same phone (Step 1 + pre-existing), PATCH primary + merge secondary into primary (non-blocking)
    ↓
HubSpot: Submit to Forms API v3 for activity tracking
    ↓
Return success → Frontend shows success state
```

## Integration Points

### HubSpot Integration

**Forms API v3:**
- Preferred method for Step 1 (preserves activity history)
- Uses `hubspotutk` cookie (hutk) for contact identification
- Automatically triggers workflows
- Form GUID: `9f9d4e35-d8d9-4283-93b6-1a789e0a1281`

**CRM API:**
- Fallback for Step 1 if Forms API fails
- Required for Step 2 (updates existing contact)
- Uses contact ID for updates
- Includes retry logic (3 attempts, exponential backoff)

**Contact Identification:**
- Step 1: Creates with temp email `lead-{leadId}@temp.ordio.com`
- Step 2: Finds by temp email **first** (unambiguous), then phone as fallback with temp email preference → Updates with real email

**Duplicate Contact Prevention:**
- Step 2 must find the exact contact created in Step 1, not a pre-existing contact with the same phone
- Lookup order: `findContactByTempEmail()` → `findContactByPhonePreferringTempEmail()` (fallback)
- **Auto-merge:** After CRM API update, if exactly 2 contacts share the same phone (Step 1 + pre-existing), `detectAndMergeDuplicates()` PATCHes the primary and merges the Step 1 contact into it. Non-blocking.
- See [DUPLICATE_CONTACT_PREVENTION.md](DUPLICATE_CONTACT_PREVENTION.md) for root cause, fix, auto-merge, and manual merge instructions

**Activity Tracking:**
- Page views tracked BEFORE contact creation (Events API)
- Forms API v3 includes hutk in context
- CRM API tracks page views separately

### Google Sheets Integration

**Authentication:**
- JWT via service account
- Credentials: `v2/config/google-sheets-credentials.json` (or embedded)
- Service account: `ordio-webinar-sheets@ordio-472310.iam.gserviceaccount.com`

**Operations:**
- **Append:** Step 1 creates new row
- **Update:** Step 2 updates existing row (by lead ID)
- **Read:** Step 2 retrieves original data

**Column Structure:**
- A: Timestamp
- B: Name
- C: Phone
- D: Email
- E: Notes
- F: Call Preference
- G: Status
- H: Source Page
- I: Lead Source
- J: Lead ID
- K: Trigger Type
- L: Source Page Label
- M: Page Context

### UTM Tracking Integration

**Frontend:**
- Uses `window.utmTracker.getAllUTMData()` (centralized tracker)
- Fallback to `window.utmTracking` or URL params
- Validates current UTM data (prevents stale cookie submission)

**Backend:**
- Receives full UTM data from frontend
- Prioritizes frontend data over cookies
- Tracks: utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid

**Lead Source Detection:**
- Paid Search (gclid or cpc/ppc medium)
- Organic Search (organic medium or search referrer)
- Meta (fb/facebook/instagram source)
- LinkedIn (linkedin source/medium)
- Social (tiktok/twitter/youtube/x source)
- Email Marketing (email medium)
- Referral (referral medium or external referrer)
- Direct Traffic (default)

### Session Management

**Submission Tracking:**
- `sessionStorage`: `lead_capture_submitted`, `lead_capture_permanent`
- `localStorage`: `lead_capture_submitted`, `lead_capture_submitted_expires` (30 days)
- Prevents form resubmission for 30 days

**Page State:**
- `sessionStorage`: `lead_capture_shown`, `lead_capture_page`
- Prevents multiple popups per page
- Resets on page navigation

**Refresh Detection:**
- Checks `performance.navigation.type`
- Clears session state on refresh
- Allows popup to show again after refresh

## File Structure

```
v2/
├── components/
│   ├── lead-capture-popup.php          # Popup component (HTML/CSS/JS)
│   └── lead-capture-copy-detector.php  # Copy detection logic
├── api/
│   └── lead-capture.php                 # API endpoint
├── js/
│   └── lead-capture-triggers.js        # Trigger system
├── data/
│   └── lead_capture_copy.php           # Copy patterns/config
└── config/
    └── google-sheets-credentials.json   # Google Sheets auth (optional)
```

## Error Handling Strategy

### Frontend Errors
- **Validation:** Real-time phone validation, email format check
- **Network:** 15s timeout, retry logic, user-friendly error messages
- **API Errors:** Display error from API response

### Backend Errors
- **Validation:** Return 400 with specific error message
- **HubSpot Failures:** Log error, continue with Sheets (fallback)
- **Sheets Failures:** Log error, continue with HubSpot (fallback)
- **Both Failures:** Send email notification, log to file

### Logging
- **Frontend:** Console warnings for UTM tracker issues
- **Backend:** `error_log()` for all operations
- **Log Files:** `v2/logs/lead-capture.log`, `v2/logs/lead-capture-debug.log`

## State Management

### Frontend State
- `leadId`: Generated on Step 1, used for Step 2
- `currentStep`: 1, 2, or 'success'
- `triggerType`: Type of trigger that opened popup
- `forceOpen`: Flag for manual triggers (bypasses restrictions)

### Backend State
- `$_SESSION['lead_capture_submitted']`: Submission flag
- `$_SESSION['lead_capture_permanent_block']`: Prevents Step 1 resubmission
- `$_SESSION['lead_capture_submitted_at']`: Timestamp

### Browser Storage
- `sessionStorage`: Page-specific state (cleared on tab close)
- `localStorage`: Persistent state (30-day expiration)

## Dependencies

### External APIs
- **HubSpot:** Forms API v3, CRM API v3, Events API
- **Google Sheets:** Sheets API v4

### Internal Dependencies
- `window.utmTracker`: UTM tracking (must be loaded before popup)
- `window.leadCaptureTriggers`: Trigger system (optional, auto-initialized)

### PHP Requirements
- `openssl`: JWT signing for Google Sheets
- `json`: JSON encoding/decoding
- `curl`: HTTP requests (if not using file_get_contents)

## Performance Considerations

### Copy Detection
- Static caching (loads config once per request)
- Quick lookup table (O(1) for common pages)
- Early exit on first match

### Trigger System
- Debounced scroll listener (requestAnimationFrame)
- Single timeout per trigger type
- Session storage checks (fast, synchronous)

### API Calls
- 15s timeout for fetch requests
- AbortController for cancellation
- Parallel processing (HubSpot + Sheets)

## Security Considerations

### Input Validation
- Server-side validation (never trust client)
- Phone normalization (prevents format bypass)
- Email format validation
- XSS prevention (htmlspecialchars)

### API Security
- Service account credentials (not user credentials)
- API tokens in config files (not in code)
- Rate limiting (session-based)

### Data Privacy
- No sensitive data in logs
- GDPR-compliant (30-day expiration)
- Secure cookie handling (hubspotutk)

## Testing Considerations

### Unit Testing
- Copy detection patterns
- Phone normalization
- UTM tracking logic
- Session management

### Integration Testing
- HubSpot contact creation
- Google Sheets sync
- Error scenarios (API failures)
- Session expiration

### E2E Testing
- Form submission flow
- Trigger behavior
- Mobile responsiveness
- Accessibility

## Future Improvements

1. **A/B Testing:** Different copy variations
2. **Analytics:** Conversion tracking per trigger type
3. **Personalization:** Dynamic copy based on user behavior
4. **Multi-step Optimization:** Reduce form abandonment
5. **Performance:** Lazy load popup component
6. **Accessibility:** Enhanced screen reader support

