πŸ“‹ Changelog

All updates and improvements to SimProSync

v1.9.3

πŸ›‘οΈ Duplicate Creation Prevention & Multiple Webhook Fixes

Latest
11 Apr 2026
  • βœ“ FIX #1: LOOP PREVENTION (v1.9.0 webhook)
  • βœ“ Job status was flip-flopping (Pending ↔ Progress) causing multiple notifications
  • βœ“ Root cause: GHLβ†’SimProβ†’GHL echo loop when both sides fired webhooks
  • βœ“ Now fetches current job/quote state BEFORE updating
  • βœ“ Skips redundant updates if Stage/Status already match (logs show "πŸ›‘ LOOP PREVENTED")
  • βœ“ Also removed AutoAdjustStatus=false which was permanently disabling SimPro's Auto Adjust
  • βœ“
  • βœ“ FIX #2: ENTITY TYPE ROUTING (v1.9.1 webhook)
  • βœ“ Creating opportunity in "Ongoing Jobs Pipeline" was creating a Quote instead of Job
  • βœ“ Root cause: ACTION code always called createSimproQuote() regardless of entity_type
  • βœ“ Now checks entity_type from pipeline_stage_mappings and routes correctly
  • βœ“ Added new createSimproJob() function for direct job creation from opportunities
  • βœ“ Logs now show "⚑ ACTION: Creating Job for Opportunity..." when entity_type=job
  • βœ“
  • βœ“ FIX #3: ARCHIVING BUG (v1.9.2 webhook)
  • βœ“ Quotes/Jobs weren't archiving when moved to "Ghosted" or "Abandoned" stages
  • βœ“ Root cause: archive_reason_id was extracted from mapping but never added to API payload
  • βœ“ SimPro requires ArchiveReason array when Stage="Archived"
  • βœ“ Now includes ArchiveReason in update payload (logs show "πŸ“‹ Adding ArchiveReason ID X")
  • βœ“
  • βœ“ FIX #4: DUPLICATE CREATION (v1.9.3 webhook)
  • βœ“ Multiple GHL workflows firing simultaneously could create duplicate jobs/quotes
  • βœ“ Root cause: No deduplication lock for job/quote creation (only sites had this)
  • βœ“ Added lock-based deduplication using acquireCreationLock() pattern
  • βœ“ First webhook creates entity; subsequent webhooks detect and skip
  • βœ“ Logs show "πŸ›‘ DEDUP: job #X already created for opportunity by another webhook"
  • βœ“
  • βœ“ ENHANCEMENT: GOOGLE DRIVE FILE NAMING
  • βœ“ Files uploaded to Google Drive now include customer name in filename
  • βœ“ Original: "photo.jpg" β†’ New: "photo - John Smith.jpg"
  • βœ“ Makes it easier to identify files in Drive without opening folders
  • βœ“ Customer name is sanitized to remove invalid filename characters
v1.9.0

πŸ—‘οΈ Deletion Manager

6 Apr 2026
  • βœ“ NEW FEATURE: Admin-only Deletion Manager for safely removing entities from both SimPro and GHL
  • βœ“
  • βœ“ πŸ” SEARCH & PREVIEW:
  • βœ“ Search for Jobs, Quotes, or Sites by ID or name
  • βœ“ Preview shows EVERYTHING that will be deleted (cascade view)
  • βœ“ Shows linked GHL Opportunities, Custom Objects, and Associations
  • βœ“ Site deletion shows all child Jobs and Quotes that will also be removed
  • βœ“
  • βœ“ πŸ›‘οΈ SAFETY FEATURES:
  • βœ“ Admin-only access - regular users cannot access deletion manager
  • βœ“ Soft-delete by default - items can be restored within configurable window (7-90 days)
  • βœ“ Must type "DELETE" to confirm soft-delete, "PERMANENT" for permanent delete
  • βœ“ Invoiced jobs are BLOCKED from deletion - must archive in SimPro instead
  • βœ“ Full audit trail of all deletion actions with user and timestamp
  • βœ“ Data snapshots stored for potential restore
  • βœ“
  • βœ“ ♻️ RESTORE & PERMANENT DELETE:
  • βœ“ Restore items from deletion queue within the expiry window
  • βœ“ Restored items get new IDs (SimPro limitation) with updated sync_links
  • βœ“ Permanent delete clears all snapshots - cannot be undone
  • βœ“ Auto-expiry cron job permanently deletes items past their restore window
  • βœ“
  • βœ“ πŸ“‹ NEW DATABASE TABLES:
  • βœ“ deletion_queue - tracks all deletion requests, status, and snapshots
  • βœ“ deletion_audit_log - full audit trail of all actions
  • βœ“ Tables auto-created on first access
v1.8.9

🏷️ Fix Duplicate Tags, Zombie Records & Attachment Display

6 Apr 2026
  • βœ“ FIX #1: Tags syncing from GHL to SimPro were creating massive duplicates
  • βœ“ Example: Dozens of identical "schools" tags with different IDs
  • βœ“ Root cause: findOrCreateSimproTagIds() was auto-creating tags when lookup failed
  • βœ“ Function now ONLY matches existing SimPro tags - never creates new ones
  • βœ“ Added whitespace trimming to tag name normalization for more robust matching
  • βœ“ To sync new tags, first create them manually in SimPro (Setup β†’ Tags)
  • βœ“
  • βœ“ FIX #2: Zombie record prevention (e.g., "Places for People" recreating)
  • βœ“ When updating a job/quote returns 404 (deleted in SimPro), the stale link is now cleared
  • βœ“ Removes orphaned references from opportunity_job_links and opportunity_quote_links
  • βœ“ Prevents the system from holding references to deleted SimPro entities
  • βœ“
  • βœ“ FIX #3: Gallery showing attachments that have been removed from server
  • βœ“ Files cleaned up after Drive sync were still appearing in the gallery
  • βœ“ Added WHERE clause filter: only show attachments where file_path is not empty
  • βœ“ Removed attachments no longer clutter the gallery view
v1.8.8

🏒 Company Customer Contact Fallback

30 Mar 2026
  • βœ“ FIX: Jobs/Quotes for company customers were not creating GHL contacts or opportunities
  • βœ“ Company customers in SimPro often have empty Email/Phone fields on the company record itself
  • βœ“ Contact details are stored on the company's Contacts (people like "Jackie" or "Joe")
  • βœ“ SimProSync was checking customer.Email which was empty, so no GHL contact could be upserted
  • βœ“ Without a GHL contact, no opportunity could be created or linked
  • βœ“ NEW: Fallback logic for company customer email/phone
  • βœ“ When customer.Email is empty, now checks customer.Contacts array
  • βœ“ Prioritises the Primary Invoice Contact (PrimaryInvoiceContact: true)
  • βœ“ Falls back to first contact with an email if no primary is set
  • βœ“ Also auto-populates firstName/lastName from the selected contact
  • βœ“ Example: "Company name Ltd" with empty Email now uses "contact@companyname.com" from contact
  • βœ“ Logging shows: "πŸ“§ Auto-populated email from company contact (contact): contact@companyname.com"
v1.8.7

🧾 Invoice Status β†’ Job Opportunity Sync

30 Mar 2026
  • βœ“ FIX: When marking an invoice as paid, linked job opportunities were not updating in GHL
  • βœ“ SimPro automatically changes job status to "Job : Invoice Fully Paid" when invoice is paid
  • βœ“ But SimProSync was not syncing this status change to the GHL opportunity
  • βœ“ Root cause: invoice.status event type was not triggering opportunity stage sync
  • βœ“ NEW: invoice.status now triggers opportunity stage updates for associated jobs
  • βœ“ When invoice.status or invoice.updated fires, fetches all associated jobs from invoice.Jobs array
  • βœ“ For each job, fetches current job data to get latest status (e.g., "Job : Invoice Fully Paid")
  • βœ“ Looks up linked GHL opportunity for each job
  • βœ“ Updates opportunity stage based on job's current status using existing pipeline mappings
  • βœ“ Supports invoices linked to multiple jobs (processes each one)
  • βœ“ New logging section: invoice_job_sync shows each job processed and result
  • βœ“ Example: Invoice paid β†’ Job #8221 status becomes "Job : Invoice Fully Paid" β†’ GHL opportunity moves to mapped stage
v1.8.6

πŸ”§ company_id=0 Bug Fixes & Settings Test Buttons

25 Mar 2026
  • βœ“ FIX: Multiple files failed for single-company SimPro accounts (company_id = 0)
  • βœ“ PHP treats 0 as "falsy", so checks like !$companyId or empty($companyId) incorrectly failed
  • βœ“ Fixed in: mappings_pipeline.php, fetch_simpro_attachments.php, force_sync.php, dropdown_resolver.php
  • βœ“ All single-company SimPro setups (like Safespark) now work correctly
  • βœ“ NEW: Test API Connection buttons on Settings page
  • βœ“ Each integration now has "Test SimPro Connection" and "Test GHL Connection" buttons
  • βœ“ SimPro test calls /setup/statusCodes/projects/ to verify credentials
  • βœ“ GHL test calls /locations/{locationId} to verify API access
  • βœ“ Shows success (green) with status count or location name
  • βœ“ Shows failure (red) with specific error message and fix instructions
  • βœ“ Helps diagnose 401 (wrong API key) vs 404 (wrong company ID) errors
  • βœ“ IMPROVED: Snapshot system now captures source status cache
  • βœ“ create_snapshot.php now stores source_status_cache when snapshotting pipeline mappings
  • βœ“ Contains status_id β†’ status_name mapping from source SimPro
  • βœ“ Enables future status ID remapping when applying to different SimPro instances
  • βœ“ Note: Status remapping only works when SimPro instances have matching status NAMES
  • βœ“ Different SimPro setups with custom statuses will need manual pipeline/status mapping
  • βœ“ Snapshot version bumped to 2.3
v1.8.4

πŸ“Έ Comprehensive Snapshots & Multiple Fixes

25 Mar 2026
  • βœ“ FIX: apply_snapshot.php was using wrong column names for association labels
  • βœ“ Changed first_object_key/second_object_key β†’ input_schema/output_schema to match actual DB schema
  • βœ“ FIX: Association mappings were looking for "is_enabled" but snapshot contains "enabled"
  • βœ“ FIX: Status mappings INSERT was completely wrong schema β€” fixed to match ghl_status_mappings table
  • βœ“ Correct columns: integration_id, custom_object_key, field_type, simpro_value, simpro_status_id, ghl_value, display_order
  • βœ“ FIX: Pipeline mappings now includes all columns from snapshot (simpro_status_id, simpro_status_ids, archive_reason_id, sync_direction, is_primary, is_default_for_new)
  • βœ“ Snapshots can now be successfully applied to new integrations like Safespark
  • βœ“ NEW: Snapshots now include ALL mapping types for complete account cloning
  • βœ“ Added: Association Mappings (Contact↔Site, Site↔Project, etc.)
  • βœ“ Added: Association Labels (GHL custom object association definitions)
  • βœ“ Added: Pipeline Stage Mappings (GHL stages ↔ SimPro stages)
  • βœ“ Added: Status Mappings (SimPro status values ↔ GHL dropdown values)
  • βœ“ Snapshot version bumped to 2.2 to reflect new comprehensive structure
  • βœ“ NEW: Events are now ENABLED by default when applying snapshots
  • βœ“ Previously, if source account had disabled events, applied accounts inherited disabled state
  • βœ“ New accounts created from snapshots now start with all events active immediately
  • βœ“ FIX: Status Mappings page failed for single-company SimPro accounts
  • βœ“ company_id = 0 is valid for single-company setups, but PHP treated it as "falsy"
  • βœ“ Changed check from if($companyId) to if($companyId !== null && $companyId !== '')
  • βœ“ SimPro statuses now load correctly for ALL accounts regardless of company_id value
  • βœ“ FIX: Settings page now respects user permissions
  • βœ“ Cloud Storage section (Google Drive, Dropbox) hidden when user lacks permission
  • βœ“ Auto-Sync settings hidden when google_drive permission is disabled
  • βœ“ Permissions loaded from user_permissions table and checked before rendering sections
  • βœ“ NEW: Opportunity name automatically syncs to Quote/Job name in SimPro
  • βœ“ Name changes are detected independently β€” no stage change required
  • βœ“ When you rename an opportunity in GHL, the linked quote/job name updates in SimPro automatically
  • βœ“ New syncOpportunityNameToSimpro() function handles name-only updates
  • βœ“ Last known name stored in ghl_opportunity_name column for change detection
  • βœ“ Works for both quotes and jobs
  • βœ“ DB: Added ghl_opportunity_name column to opportunity_quote_links and opportunity_job_links tables
  • βœ“ UI: New "Additional Mappings" section in Create Snapshot modal
  • βœ“ UI: Info boxes explain snapshot behavior and event enabling
  • βœ“ UI: Association, Pipeline, and Status mappings checked by default when creating snapshots
v1.8.3

⚑ SimPro Webhook Timeout Prevention

24 Mar 2026
  • βœ“ FIX: SimPro webhooks failing with HTTP 408 (Request Timeout) during bulk operations
  • βœ“ SimPro was timing out waiting for SimProSync to respond while processing was still running
  • βœ“ NEW: Implemented fastcgi_finish_request() pattern in webhook_simpro.php
  • βœ“ Webhook endpoint now sends 200 OK immediately upon receiving valid JSON
  • βœ“ Processing continues in background after SimPro connection is closed
  • βœ“ Eliminates 408 errors even when enrichment requires 20-30 API calls
  • βœ“ Database logging still captures full processing results via shutdown function
  • βœ“ FIX: PHP warning "Undefined variable $ghlContactId" in webhook_processor.php
  • βœ“ Variable was referenced in diff logging section before being initialized
  • βœ“ Added proper initialization: $ghlContactId = null before the logging block
  • βœ“ Server timeout configuration also increased (max_execution_time: 300s, nginx proxy timeouts: 300s)
v1.8.2

πŸ”§ Server Timeout Configuration

24 Mar 2026
  • βœ“ INFRASTRUCTURE: Increased PHP max_execution_time from 30s to 300s
  • βœ“ INFRASTRUCTURE: Added nginx proxy timeout directives (300s) for webhook endpoints
  • βœ“ Prevents 504 Gateway Timeout errors during bulk SimPro operations
  • βœ“ Each webhook with full enrichment can require 10-15 seconds of API calls
  • βœ“ Bulk updates (e.g., updating many jobs at once) previously caused server to become unresponsive
  • βœ“ Note: This changelog entry documents server-side Plesk configuration changes
v1.8.1

🎯 Auto-Create Opportunity Fix

17 Mar 2026
  • βœ“ FIX: Auto-create opportunity logic was incorrectly blocking opportunity creation
  • βœ“ The "auto_create_opportunity" setting was being checked for ALL jobs/quotes, not just new contacts
  • βœ“ This caused opportunities to NOT be created when jobs/quotes came in for existing GHL contacts
  • βœ“ REMOVED: The setting check wrapper that was blocking createGhlOpportunity() calls
  • βœ“ Now opportunities are created for ALL jobs/quotes that have a valid GHL contact linked
  • βœ“ The original intent was to control opportunity creation for brand new contacts only
  • βœ“ Existing contact workflows now work correctly: Job created in SimPro β†’ Opportunity created in GHL
  • βœ“ No changes to association logic, custom object sync, or field mappings
v1.8.0

πŸ“Š Opportunity Custom Fields & Company Customer Fixes

16 Mar 2026
  • βœ“ FIX: Opportunity custom fields now sync from SimPro to GHL
  • βœ“ Previously, mappings for opportunity.* fields (SimPro Job ID, Opportunity Value, Site ID, etc.) were ignored
  • βœ“ Code was only handling contact.*, custom_objects.*, and contact.phone.* fields
  • βœ“ NEW: Mapping loop now collects opportunity.* fields into $opportunityFieldMappings array
  • βœ“ Opportunity fields are written when creating new opportunities (if auto-create enabled)
  • βœ“ Opportunity fields are written when updating existing linked opportunities
  • βœ“ Field ID lookup tries both "opportunity.fieldname" and "fieldname" formats (GHL API compatibility)
  • βœ“ FIX: Company customer sites URL now includes customer type (GHL β†’ SimPro direction)
  • βœ“ Previously, creating quotes/jobs from GHL failed for Company customers with 404 errors
  • βœ“ Sites endpoint was using /customers/{id}/sites/ instead of /customers/companies/{id}/sites/
  • βœ“ Now correctly detects customer type and uses appropriate endpoint
  • βœ“ Applies to: createSimproQuote(), createSimproEntityFromProject(), and related functions
  • βœ“ NEW: "Opportunity Custom Fields" section in SimPro Logs page
  • βœ“ Shows which opportunity fields were collected and their values
  • βœ“ Helps debug opportunity field sync issues
  • βœ“ Processing Steps now show: "πŸ“‹ Collected opportunity field: opportunity.simpro_job_id = 12345"
v1.7.9

🏒 Company Contact Fallback & Mapping-Driven Opportunity Value

5 Mar 2026
  • βœ“ NEW: Company customer contact fallback for SimProβ†’GHL direction
  • βœ“ Previously, jobs linked to Company customers with no direct email/phone failed with "No email or phone available"
  • βœ“ System now detects when customer is a Company with blank contact details
  • βœ“ Falls back to the job's CustomerContact (the specific person assigned to the job)
  • βœ“ Fetches full contact details from SimPro: /customers/{id}/contacts/{contactId}
  • βœ“ Extracts email, phone (CellPhone or WorkPhone), firstName, lastName from the contact record
  • βœ“ Enables contact upsert, opportunity sync, and project/site associations for Company customer jobs
  • βœ“ Fallback only activates when email AND phone are both empty β€” no impact on Individual customers
  • βœ“ Clear logging: "πŸ“§ Using CustomerContact email (company fallback): ..."
  • βœ“ NEW: Mapping-driven opportunity value sync
  • βœ“ Opportunity value (monetaryValue) is now read from ghl_field_mappings for the lead_value field
  • βœ“ Customers can map any SimPro value field to GHL opportunity value via the Mappings UI
  • βœ“ Falls back to Total.ExTax β†’ Total.IncTax if no mapping configured
  • βœ“ New helper function: getOpportunityValueFromMappings() used in both createGhlOpportunity() and stage sync
  • βœ“ FIX: Dropdown resolver now removes unresolvable dropdown values from payload entirely
  • βœ“ Previously, invalid dropdown values were left in the payload causing SimPro API errors
  • βœ“ Acts as a safety net for customer misconfiguration of dropdown field mappings
v1.7.8

🏒 Company Customer Support & Customer Type Storage

3 Mar 2026
  • βœ“ FIX: Customer updates now work for BOTH Individual and Company customers
  • βœ“ Previously, all customers were assumed to be Individuals, causing 404 errors when updating Company customers
  • βœ“ NEW: simpro_customer_type column in sync_links table stores customer type (individuals/companies)
  • βœ“ Customer type is now stored when a customer is created via GHLβ†’SimPro sync
  • βœ“ When updating, system checks sync_links first for stored type (avoids extra API call)
  • βœ“ If type not stored, detects from SimPro API and stores for future use
  • βœ“ NEW: Helper functions: getStoredCustomerType(), storeCustomerType(), detectSimproCustomerType()
  • βœ“ Detection logic improved: checks SimPro Type field AND CompanyName presence
  • βœ“ Company customers now correctly use /customers/companies/ endpoint
  • βœ“ Individual customers continue using /customers/individuals/ endpoint
  • βœ“ Backwards compatible: Existing sync_links records without type will auto-detect on next update
  • βœ“ FIX: PHP parse error in webhook_processor.php (duplicate empty function declaration)
  • βœ“ FIX: Undefined variable $ghlContactId warning in webhook_processor.php diff logging section
v1.7.7

πŸ” Site Deduplication - Prevent Duplicate GHL Sites

3 Mar 2026
  • βœ“ FIX: Duplicate Sites created when manually adding Sites in GHL before SimPro sync
  • βœ“ NEW: Multi-level Site deduplication in SimProβ†’GHL direction
  • βœ“ Level 1: Check site_sync_links table for existing link by SimPro Site ID
  • βœ“ Level 2: Search GHL by site_id field (catches Sites created by SimProSync)
  • βœ“ Level 3: Search GHL by site_name field (catches manually created Sites)
  • βœ“ Level 4: Search GHL by site_address + site_postal_code (catches Sites with different names but same location)
  • βœ“ When match found: Updates existing Site and writes SimPro Site ID to link them
  • βœ“ Only creates new Site if ALL dedup checks fail
  • βœ“ NEW: getGhlObjectRecord() helper function for fetching GHL custom object records
  • βœ“ Logs clearly show which dedup method matched: "Found existing Site by name/address+postcode"
v1.7.6

🏷️ Tags Sync Fix & Webhook 500 Error Resolution

3 Mar 2026
  • βœ“ FIX: Tags now sync correctly to SimPro (was sending tag names, now sends tag IDs)
  • βœ“ SimPro API requires Tags as array of integer IDs, not string names
  • βœ“ NEW: findOrCreateSimproTagIds() function resolves tag names to SimPro Tag IDs
  • βœ“ Auto-creates tags in SimPro if they don't exist (via POST /setup/tags/)
  • βœ“ Tags from GHL (comma-separated string) are parsed, resolved to IDs, and sent correctly
  • βœ“ FIX: 500 error on SimPro β†’ GHL webhooks caused by function redeclaration
  • βœ“ Wrapped duplicate functions with function_exists() checks to prevent conflicts
  • βœ“ Affects: webhook_processor.php and functions.php sharing common functions
  • βœ“ Loop prevention confirmed working: 60-second throttle prevents infinite sync loops
v1.7.5

πŸŽ›οΈ Dynamic Custom Object Mapping & Flexible Entity Types

2 Mar 2026
  • βœ“ NEW: Dynamic GHL Custom Object Mapping in Settings page
  • βœ“ Users can now map ANY GHL custom object to ANY SimPro entity type (Sites, Projects, Quotes, Jobs, Leads, Invoices)
  • βœ“ No longer limited to hardcoded "sites" and "projects" object names
  • βœ“ Settings UI shows a table of all discovered custom objects with dropdown to select SimPro entity type
  • βœ“ Auto-detection: Object names containing "site/location/property" default to Sites, "project/job/work" default to Projects, etc.
  • βœ“ Mappings stored in new ghl_custom_object_mappings database table
  • βœ“ NEW: SimPro entity type inference from GHL custom object key
  • βœ“ When custom object webhook arrives, system looks up configured entity type
  • βœ“ Falls back to smart detection based on object name if no mapping configured
  • βœ“ Unified webhook handler now routes to correct SimPro entity creation based on mapping
  • βœ“ FIX: Custom object field mappings now load correctly in GHL Mappings UI
  • βœ“ Field dropdowns now populate with custom object properties
v1.7.4

πŸ”— Enhanced Association System & Object-to-Object Links

1 Mar 2026
  • βœ“ NEW: Object-to-Object associations (Sites ↔ Projects) now fully supported
  • βœ“ Previously only Contact ↔ Custom Object associations were created
  • βœ“ System now creates associations between custom objects when both exist
  • βœ“ Example: When a Quote syncs with Site, the Quote's Project is linked to the Site
  • βœ“ NEW: Association processing split into two phases
  • βœ“ Phase 1: Contact ↔ Custom Object associations (immediate)
  • βœ“ Phase 2: Object ↔ Object associations (after all objects processed)
  • βœ“ Ensures both objects exist before attempting to link them
  • βœ“ FIX: Association errors no longer block the main sync process
  • βœ“ Errors are logged but processing continues
  • βœ“ Clear logging shows which associations succeeded, failed, or were skipped
v1.7.3

πŸ“ Site Sync Improvements & Address Handling

28 Feb 2026
  • βœ“ FIX: Site address fields now sync correctly to GHL custom objects
  • βœ“ Site address, city, state, postal code, and country now map to corresponding GHL fields
  • βœ“ NEW: Automatic site sync when job/quote has associated site
  • βœ“ When a job or quote syncs, its linked Site is automatically synced to GHL
  • βœ“ Site ↔ Contact association created automatically
  • βœ“ Site ↔ Project association created if project exists
  • βœ“ FIX: Site sync no longer fails when Site has no address data
  • βœ“ Empty address fields are handled gracefully
  • βœ“ NEW: site_sync_links table tracks SimPro Site ID to GHL Site custom object ID
  • βœ“ Prevents duplicate Site creation on subsequent syncs
v1.7.2

πŸ”„ Loop Prevention Enhancements

25 Feb 2026
  • βœ“ FIX: Loop prevention now correctly handles rapid sequential webhooks
  • βœ“ Previous 60-second throttle was too aggressive for legitimate batch updates
  • βœ“ NEW: Smart entity comparison - only blocks if Stage AND Status unchanged
  • βœ“ Allows legitimate updates while still preventing infinite loops
  • βœ“ NEW: GHL echo detection via sync_links table
  • βœ“ When GHL webhook arrives, checks if we just sent that update
  • βœ“ Prevents "we changed it β†’ GHL echoed it β†’ we changed it again" loops
  • βœ“ Improved logging shows exactly why a webhook was blocked or allowed
v1.7.1

πŸ“Š Enrichment Optimization & Hash-Based Skip

20 Feb 2026
  • βœ“ NEW: Hash-based enrichment optimization for SimProβ†’GHL direction
  • βœ“ Hashes webhook payload + enrichment data to detect duplicate content
  • βœ“ Skips processing if hash matches recent webhook (within 24 hours)
  • βœ“ Significantly reduces unnecessary GHL API calls
  • βœ“ NEW: webhook_data_hashes table stores recent hashes with auto-cleanup
  • βœ“ Hashes auto-expire after 24 hours to prevent table bloat
  • βœ“ Clear logging: "⏭️ Skipping - identical data recently processed (hash: abc123)"
  • βœ“ FIX: Enrichment no longer fetches data that won't be used
  • βœ“ Only fetches related entities that have active field mappings
v1.7.0

πŸš€ Unified GHL Webhook Handler

15 Feb 2026
  • βœ“ NEW: Single unified webhook endpoint for ALL GHL events
  • βœ“ Replaces multiple individual webhook handlers
  • βœ“ All GHL webhooks now route through actions/webhook_ghl_unified.php
  • βœ“ Simplified webhook configuration - one URL for all events
  • βœ“ NEW: Modular architecture with separate helper files
  • βœ“ core/ghl/discovery.php - GHL API discovery and custom fields
  • βœ“ core/ghl/simpro_api.php - SimPro API calls
  • βœ“ core/ghl/ghl_api.php - GHL API calls
  • βœ“ core/ghl/enrichment.php - Data enrichment logic
  • βœ“ core/ghl/pipeline.php - Pipeline stage handling
  • βœ“ core/ghl/payload_builder.php - SimPro payload construction
  • βœ“ core/ghl/helpers.php - Utility functions
  • βœ“ core/ghl/simpro_dedup.php - SimPro deduplication
  • βœ“ Improved error handling and logging throughout
v1.6.0

SimPro β†’ GHL Webhook Processor

10 Jan 2026
  • βœ“ NEW: SimPro β†’ GHL webhook processor with full enrichment
  • βœ“ Enriches SimPro webhooks with customer, site, job history, quotes, invoices, notes
  • βœ“ Smart field mapping from SimPro fields to GHL contact and custom fields
  • βœ“ Pipeline stage sync from SimPro job/quote stages to GHL opportunity stages
  • βœ“ Opportunity creation and linking with jobs/quotes
  • βœ“ Contact ↔ Site ↔ Project associations in GHL
v1.5.8

Company vs Individual Customer Payload Fix

26 Dec 2025
  • βœ“ Fixed HTTP 422 errors when creating Company customers
  • βœ“ Company customers no longer send GivenName/FamilyName at top level
  • βœ“ Individual customers no longer send nested "contact" object
  • βœ“ Fixed cascade customer creation in site.create and job.create
  • βœ“ Fixed fallback CREATE scenario payload cleanup
v1.5.7

GHL β†’ SimPro Custom Object Create Fix

25 Dec 2025
  • βœ“ Fixed site.create_object was not looking up Customer ID from associated contact
  • βœ“ Fixed job.create_object was not looking up Customer ID from associated contact
  • βœ“ Fixed quote.create_object was not looking up Customer ID from associated contact
  • βœ“ Fixed lead.create_object was not looking up Customer ID from associated contact
  • βœ“ All *.create_object handlers now properly call getSimproCustomerIdFromAssociatedContact()
  • βœ“ Sites, Jobs, Quotes, and Leads can now be created from GHL custom objects when linked to a contact
v1.5.6

Object-to-Object Associations Fix

25 Dec 2025
  • βœ“ Fixed Sites ↔ Projects associations not being called
  • βœ“ Association functions are now properly invoked after successful API calls
  • βœ“ Improved logging for association creation
v1.5.0

Enrichment Engine & Smart Mapping Analysis

20 Dec 2025
  • βœ“ Added GHL Enrichment Engine - fetches full contact, business, opportunities, and custom objects
  • βœ“ Smart Mapping Analysis now shows all available fields from enriched data
  • βœ“ Improved field lookup with unified GHL data
  • βœ“ Added support for custom object properties in mappings
v1.4.0

Quote Creation, ID Writebacks & Safety Checks

13 Dec 2025
  • βœ“ Fixed quote creation - Customer now sent as integer, auto-fetches Site/Type/Name
  • βœ“ Fixed enrichment to use correct contact_id instead of opportunity ID
  • βœ“ Added Company vs Individual customer auto-detection
  • βœ“ Improved Contact events handling (separate from Customer events)
  • βœ“ Added safety check to prevent accidental mapping deletion
  • βœ“ Fixed Site ID writeback API URL bug
  • βœ“ Added Job ID writeback for custom objects
  • βœ“ Added Quote ID writeback for custom objects
  • βœ“ Added smart fallbacks for site_id, job_id, quote_id field names
  • βœ“ Removed debug logging code
v1.3.0

Site Update Fixes & Log Improvements

10 Dec 2025
  • βœ“ Fixed site update failures - removed trailing slashes from PATCH URLs
  • βœ“ Fixed site.create_object payload structure
  • βœ“ Improved log display with loop prevention detection
  • βœ“ Added "No Action" status display
  • βœ“ Added detailed SimPro API Calls section in logs
v1.2.0

Site ID Writeback & Dynamic Field Mapping

8 Dec 2025
  • βœ“ Made Site ID writeback fully dynamic using ghl_field_mappings table
  • βœ“ Supports any custom object name and field name
  • βœ“ Improved association handling between custom objects and contacts
v1.1.0

Pipeline Stage Mapping

1 Dec 2025
  • βœ“ Added bi-directional pipeline stage sync
  • βœ“ SimPro job stages now sync to GHL opportunity stages
  • βœ“ GHL opportunity stage changes sync back to SimPro
  • βœ“ Added pipeline stage mapping configuration page
v1.0.0

Initial Release

15 Nov 2025
  • βœ“ Bi-directional sync between SimPro and GoHighLevel
  • βœ“ Customer, Site, Job, Quote, Lead, Invoice sync
  • βœ“ Custom object support
  • βœ“ Field mapping configuration
  • βœ“ Event toggle controls

SimProSync by My Local Trades