Deduplicating Company Records From a Practice-Management CRM
Company records synced from Xero Practice Manager kept re-creating themselves in HubSpot. HubSpot's native Company deduplication checks for a domain or a website, and most of the records XPM was feeding it had neither: trusts, self-managed super fund trustees, and single-purpose companies with no web presence to key on. The rebuild replaced name-based matching with XPM's own Client Export Code, a unique identifier already stamped on every exported record, and re-keyed the sync to match on it instead.
Executive Summary
Context
The client is a multi-division professional-services advisory group whose accounting and tax practice ran its client and job records through Xero Practice Manager, one of several practice-management systems feeding a new HubSpot CRM. Most of its client entities were structured as trusts, SMSFs, or single-purpose companies rather than ordinary trading businesses, so the signals HubSpot uses to tell one Company record from another were never populated.
What We Built
We rebuilt the XPM-to-HubSpot Company sync around XPM's Client Export Code, replacing the name-matching logic that had been quietly duplicating trust and SMSF records since the integration went live, and re-keyed the Jobs-to-Company association to run against the same identifier.
Tech Stack
- HubSpot Sales Hub, HubSpot Operations Hub, Xero Practice Manager (XPM), Client Export Code match
Not a fit if your CRM companies are ordinary trading businesses that already carry a domain and a website. HubSpot's native deduplication handles that case on its own, and a custom identifier match adds a system to maintain that a client in that position doesn't need. This build only pays off when a real share of your accounts are trusts, SMSFs, or other entities with no web presence for HubSpot's own matching to key on.
The Challenge
Client and job records flowed out of Xero Practice Manager into HubSpot Companies, one entity at a time: trusts, SMSF trustees, holding companies, and single-purpose Pty Ltd companies set up around individual client structures. HubSpot's own deduplication needs a domain or a website to tell two Company records apart, and most of these entities didn't have either. So the sync had nothing reliable to match on beyond the name it was given.
A trust or company registered more than once under a slightly different name became a second Company record instead of an update to the first. The "Sales Unit Trust" record existed multiple times in the portal before anyone caught it. And because every duplicate attracted jobs that XPM had already associated to that client, a single client's history ended up split across two or three records.
Our Approach
We first matched Companies by name alone, because it was the field both systems already filled in by default. It broke down against the entity types an accounting practice actually carries, such as two trusts with near-identical names, or a company re-registered under a slightly different string. Each mismatch created a new Company record instead of updating the one that already existed, and the jobs tied to that client scattered across the copies.
So we dropped name matching for XPM's own Client Export Code, a unique identifier that's already stamped on every exported record. We re-keyed both the Company sync and the Jobs-to-Company association to run against it. A batch load now checks the code before it writes a Company record, and it only creates a new one when the code has never been seen.
The trade-off is timing. XPM itself recommends automating this load as the next phase, but for now a newly created client waits for the next batch before it resolves cleanly in HubSpot.
Impact
Duplicate company records stop compounding
The deduplication workflow, keyed on the Client Export Code, catches a re-exported XPM client before it becomes a second Company record, so the duplicate count is already decreasing as each batch load runs. A trust or SMSF that used to spawn a fresh copy on every name variant now resolves to the one record it should always have been.
A client's job history reads as one record, not three
Because Jobs-to-Company inherits the same Client Export Code, a client's XPM jobs attach to the single Company record that survives deduplication, not to whichever duplicate existed when the job synced. A team pulling up that client's HubSpot record sees its full job history in one place.
The match survives structures a domain-based system can't see
Trusts, SMSF trustees, and single-purpose companies, the entity types that make up most of an accounting practice's book, now match reliably even though none carry the domain or website HubSpot's own logic looks for. The identifier behind the match comes from the practice-management system itself, so it holds regardless of what a Company record has on the web.
The object model is ready for its next system
Checking the design against HubSpot's own record and association ceilings before cutover meant that go-live didn't stall on a data-model problem found after the fact. The same Client Export Code that resolved the deduplication problem is what the next practice-management system will need to match against.
The unique identifier XPM stamps on every exported client and job record. Company sync and Jobs-to-Company both key on it instead of the entity name, so a re-exported or renamed client resolves to the Company record that already exists.
A scheduled load that checks the Client Export Code against existing Companies before writing a new one, run manually rather than on a live sync. XPM's own recommendation is to automate this load once the identifier match is proven out.
The association that attaches a client's XPM jobs to its HubSpot Company record, one of HubSpot's many-to-many associations, rebuilt to run on the Client Export Code so a job resolves to the single deduplicated Company rather than whichever duplicate existed when it last synced.
A one-time review confirming the Company and Jobs data model would hold under HubSpot's ceilings on custom objects, a 1,000,000-record cap per object and a 10,000-record association cap, checked against the entity relationship diagrams mapped during discovery before cutover to production.
Xero Practice Manager exports client, company and job records in a batch, and each one is checked against its Client Export Code before anything is written to HubSpot. A code that's already in HubSpot updates the existing Company, and a code that hasn't been seen creates a new one. Job records follow the same code into the Jobs-to-Company association, so every job lands on the one deduplicated Company. The dashed line shows the earlier name-based match that this replaced.
FAQ
HubSpot's native Company deduplication needs a domain or a website to compare records against, and it doesn't have a fallback when a record has neither. If a meaningful share of your Companies are trusts, SMSFs, or single-purpose entities without a web presence, which describes most accounting and advisory practices, the native match has nothing to run on and keeps creating duplicates instead of catching them.
You key it on whatever unique identifier your source system already assigns to each client or company record, XPM's Client Export Code in this build, rather than the name or the missing domain. The same identifier should carry through to any downstream association, like jobs or cases, so a duplicate never gets the chance to fork a client's history across two records.
Continue reading