The challenge: a CRM and an ERP that never talked
CoolKit builds custom refrigerated van conversions for UK fleet operators, dealers, and direct buyers, a business of roughly 35,000 customers. It ran a GoHighLevel CRM for sales on one side and a legacy MS SQL Server and Access ERP for quoting and operations on the other.
The two systems had no connection. Sales updated the CRM while quotes, deals, and customer records were created and changed in the ERP, so records drifted out of date, contacts duplicated, and neither system held the full truth about a customer.
What we built: bidirectional near-real-time sync
We built an integration that keeps the on-prem ERP and GoHighLevel continuously in sync, in both directions:
- Webhook bridge: a Vercel-hosted service that calls the GoHighLevel v2 API to create and update contacts, opportunities, custom-object records, and notes.
- On-prem QueueProcessor: a TypeScript service under PM2 that polls a SQL Server queue plus a neutral Supabase queue, then writes results back to SQL Server.
- SQL Server triggers: ERP changes are emitted onto the queue the moment they happen, so updates flow to the CRM without a batch window.
- Dedup-safe resolution: a create-or-match chain (person id, then customer id, then email, then phone) that resolved a real duplicate-contact incident and stops the CRM re-forking records.
We also configured the Sales (6 stages), Operations (7 stages), and Customer Service pipelines in GoHighLevel, and ran a full historical backfill so the CRM opened with the ERP’s real deal and quote history rather than a blank slate.
The result
The backfill reconciled 37,153 deals and 65,549 quotes, over 100,000 records, at 98% automatic-match coverage. Day to day, a change on either side lands on the other in about six seconds. It runs in production on live data, backed by a go-live governance pack and one-command reversibility.
Running fragmented sales and ERP systems? Let’s talk.