# HubSpot Integration Workflow


**Last Updated:** 2025-11-20

## Overview

The lead capture system integrates with HubSpot using two APIs: Forms API v3 (preferred) and CRM API v3 (fallback). This document explains the workflow, decision logic, and best practices.

## API Selection Decision Tree

### Step 1: Initial Lead Creation

```
User submits Step 1 (name + phone, no email)
    ↓
Generate temporary email: lead-{leadId}@temp.ordio.com
    ↓
Try Forms API v3 (preserves activity history)
    ↓
Success? → Create contact with temp email
    ↓
Failure? → Fallback to CRM API v3
    ↓
Success? → Create contact with temp email
    ↓
Failure? → Log error, continue with Google Sheets
```

### Step 2: Lead Update

```
User submits Step 2 (email + notes + preference)
    ↓
Find contact by temp email or phone
    ↓
Contact found? → Update via CRM API v3 (replace temp email with real email)
    ↓
Success? → Submit to Forms API v3 for activity tracking
    ↓
Contact not found? → Create new contact via CRM API v3
    ↓
Success? → Submit to Forms API v3 for activity tracking
```

## Forms API v3 (Preferred)

### When to Use

- **Step 1:** Always preferred (preserves activity history)
- **Step 2:** After CRM API update (for activity tracking only)

### Advantages

- Preserves activity history via `hubspotutk` (hutk)
- Automatically triggers workflows
- Links submissions to existing visitor sessions
- Better activity tracking in HubSpot timeline

### Implementation

```php
function submitToHubSpotFormsAPI($name, $phone, $email, $sourcePage, $leadSource, $triggerType, $leadId, $pageUrl, $referrer, $hubspotutk, $utmData, $formGuid) {
    $url = "https://api.hubapi.com/marketing/v3/forms/{$formGuid}/submit";
    
    $data = [
        'fields' => [
            ['name' => 'firstname', 'value' => $name],
            ['name' => 'phone', 'value' => $phone],
            ['name' => 'email', 'value' => $email],
        ],
        'context' => [
            'hutk' => $hubspotutk, // Critical for activity tracking
            'pageUri' => $pageUrl,
            'pageName' => $sourcePage,
        ],
        'legalConsentOptions' => [
            'consent' => [
                'consentToProcess' => true,
                'text' => 'I agree to allow Ordio to store and process my personal data.',
                'communications' => []
            ]
        ]
    ];
    
    // Add UTM data as hidden fields
    if (!empty($utmData['utm_source'])) {
        $data['fields'][] = ['name' => 'utm_source', 'value' => $utmData['utm_source']];
    }
    // ... other UTM fields
    
    // Submit to HubSpot
    $response = curl_exec($ch);
    return json_decode($response, true);
}
```

### Form GUID

- **Form GUID:** `9f9d4e35-d8d9-4283-93b6-1a789e0a1281`
- **Portal ID:** `145133546`
- **Location:** Defined in `lead-capture.php` as `ORDIO_HUBSPOT_LEAD_CAPTURE_FORM_GUID`

## CRM API v3 (Fallback/Update)

### When to Use

- **Step 1:** Fallback if Forms API v3 fails
- **Step 2:** Required for updating existing contact (replacing temp email)

### Advantages

- More control over contact properties
- Can update existing contacts by ID
- Better for complex updates

### Implementation

```php
function createHubSpotContact($name, $phone, $email, $notes, $callPreference, $sourcePage, $leadSource, $triggerType, $leadId, $berlinTime, $utmValidation, $pageUrl, $referrer, $hubspotutk) {
    $url = 'https://api.hubapi.com/crm/v3/objects/contacts';
    
    $properties = [
        'firstname' => $name,
        'phone' => $phone,
        'email' => $email,
        'lead_source' => $leadSource,
        'source_page' => $sourcePage,
        'trigger_type' => $triggerType,
        'lead_id' => $leadId,
    ];
    
    // Add UTM properties
    if (!empty($utmData['utm_source'])) {
        $properties['utm_source'] = $utmData['utm_source'];
    }
    // ... other UTM properties
    
    $data = ['properties' => $properties];
    
    // Submit to HubSpot
    $response = curl_exec($ch);
    return json_decode($response, true);
}
```

### Contact Identification

**Step 1:** Creates contact with temporary email `lead-{leadId}@temp.ordio.com`

**Step 2:** Finds contact by:
1. Temporary email (preferred)
2. Phone number (fallback)

```php
function checkExistingContact($phone, $email) {
    // Search by email first
    if (!empty($email)) {
        $url = "https://api.hubapi.com/crm/v3/objects/contacts/search";
        $data = [
            'filterGroups' => [[
                'filters' => [[
                    'propertyName' => 'email',
                    'operator' => 'EQ',
                    'value' => $email
                ]]
            ]]
        ];
        // ... search logic
    }
    
    // Search by phone if email not found
    if (!empty($phone)) {
        $normalizedPhone = normalizePhoneNumber($phone);
        // ... search by phone
    }
    
    return ['contactId' => $contactId, 'email' => $email, 'phone' => $phone];
}
```

## Temporary Email Pattern

### Format

```
lead-{leadId}@temp.ordio.com
```

**Example:** `lead-LC1234567890123@temp.ordio.com`

### Why Temporary Email?

- HubSpot Forms API v3 requires email field
- Step 1 doesn't collect email (only name + phone)
- Temporary email allows contact creation
- Easy to identify and filter in HubSpot
- Valid domain format (HubSpot validates emails)

### Benefits

- Unique per lead (leadId ensures uniqueness)
- Easy to filter (`@temp.ordio.com` in HubSpot)
- Valid email format (HubSpot accepts)
- Replaced with real email in Step 2

## Activity Tracking

### Pre-Submission Tracking

Page views tracked BEFORE contact creation using Events API:

```php
function trackPageViewBeforeCreation($hubspotutk, $pageUrl, $referrer, $utmData) {
    $url = 'https://api.hubapi.com/events/v3/send';
    
    $event = [
        'eventName' => 'page_view',
        'occurredAt' => time() * 1000, // milliseconds
        'properties' => [
            'page_url' => $pageUrl,
            'referrer' => $referrer,
            'utm_source' => $utmData['utm_source'] ?? '',
            // ... other UTM properties
        ]
    ];
    
    // Include hubspotutk for contact identification
    $headers = [
        'Authorization: Bearer ' . HUBSPOT_API_TOKEN_LEAD_CAPTURE,
        'Content-Type: application/json',
    ];
    
    if (!empty($hubspotutk)) {
        $headers[] = 'Cookie: hubspotutk=' . $hubspotutk;
    }
    
    // Submit event
    curl_exec($ch);
}
```

### Post-Submission Tracking

Forms API v3 automatically tracks form submission in activity timeline:

- Form submission appears in contact timeline
- Includes all form field values
- Links to page view (via hutk)
- Triggers workflows

## Error Handling

### Retry Logic

```php
function submitWithRetry($function, $maxAttempts = 3) {
    $attempt = 0;
    $delay = 1; // seconds
    
    while ($attempt < $maxAttempts) {
        try {
            $result = $function();
            if ($result['success']) {
                return $result;
            }
        } catch (Exception $e) {
            error_log("Attempt " . ($attempt + 1) . " failed: " . $e->getMessage());
        }
        
        $attempt++;
        if ($attempt < $maxAttempts) {
            sleep($delay);
            $delay *= 2; // Exponential backoff
        }
    }
    
    return ['success' => false, 'error' => 'Max attempts reached'];
}
```

### Error Logging

All HubSpot API errors logged to:
- `v2/logs/lead-capture-debug.log`
- Includes full API response
- Includes correlation ID for tracking

## Best Practices

1. **Always use Forms API v3 for Step 1:** Preserves activity history
2. **Use CRM API for Step 2 updates:** Can replace temp email with real email
3. **Track page views before creation:** Links activity to contact
4. **Include hubspotutk (hutk):** Critical for contact identification
5. **Normalize phone numbers:** Prevents duplicate contacts
6. **Retry on failure:** 3 attempts with exponential backoff
7. **Log all errors:** Helps debugging and monitoring

## Configuration

### API Token

```php
define('HUBSPOT_API_TOKEN_LEAD_CAPTURE', getenv('HUBSPOT_API_TOKEN') ?: 'pat-eu1-...');
```

**Location:** `v2/api/lead-capture.php`

**Security:** Store in environment variable or config file (not in code)

### Form GUID

```php
define('ORDIO_HUBSPOT_LEAD_CAPTURE_FORM_GUID', '9f9d4e35-d8d9-4283-93b6-1a789e0a1281');
```

**Location:** `v2/api/lead-capture.php`

**How to find:** HubSpot → Marketing → Lead Capture → Forms → Form Settings → Form GUID

### Portal ID

```php
define('ORDIO_HUBSPOT_PORTAL_ID', '145133546');
```

**Location:** `v2/api/lead-capture.php`

**How to find:** HubSpot → Settings → Account Setup → Account Information → Portal ID

## Testing

### Test HubSpot Connectivity

```
Visit: /v2/api/lead-capture.php?debug_hubspot=test
```

Returns:
```json
{
    "debug_test": true,
    "hubspot_api_test": {
        "http_code": 200,
        "api_accessible": true
    }
}
```

### Test Contact Creation

```bash
curl -X POST http://localhost:8003/v2/api/lead-capture.php \
  -H "Content-Type: application/json" \
  -d '{
    "step": "1",
    "name": "Test User",
    "phone": "+49123456789",
    "source_page": "/test",
    "lead_source": "Test"
  }'
```

### Verify in HubSpot

1. Go to HubSpot → Contacts
2. Search for `lead-LC*@temp.ordio.com`
3. Verify contact created with correct data
4. Check activity timeline for form submission

## Troubleshooting

### Contact Not Created

1. Check API token (valid, not expired)
2. Check form GUID (matches HubSpot form)
3. Check logs for API errors
4. Test connectivity: `/v2/api/lead-capture.php?debug_hubspot=test`

### Duplicate Contacts

1. Check phone normalization (E.164 format)
2. Check contact identification logic
3. Verify temporary email pattern matches
4. Check if contact exists before creating

### Activity Not Tracked

1. Verify hubspotutk (hutk) included in Forms API request
2. Check page view tracking (Events API)
3. Verify form GUID matches HubSpot form
4. Check activity timeline in HubSpot

## Related Documentation

- [Architecture Overview](./ARCHITECTURE.md) - System architecture
- [Google Sheets Workflow](./GOOGLE_SHEETS_WORKFLOW.md) - Google Sheets integration
- [Troubleshooting Guide](./TROUBLESHOOTING.md) - Common issues

