A support agent opens a ticket from dana@northwind.example. The help desk shows a contact created eleven days ago with no company, no history and no phone number. In the CRM, the same human has been a named contact for three years, sits under a parent account with four subsidiaries, and has an open renewal worth more than the refund she is currently being told is out of policy.
Nothing is broken, in the sense that no error was logged. The two systems are doing exactly what they were configured to do. The architecture is what is wrong: the ticket’s contact and the CRM’s contact are two rows in two databases, joined at runtime by a string that the customer controls.
The identity key is an email address, and an email address is not a person
Almost every help-desk-to-CRM integration resolves identity the same way: match on the email address, create the contact if no match is found. That rule is simple enough to implement in an afternoon and wrong in at least four common cases.
An email address is a routing label. It is shared (accounts@, support@, orders@), it is transferred (a new hire inherits ops@), it is abandoned (someone changes employer and keeps replying from a personal address), and it is aliased (the same mailbox receives mail at three addresses). A person is none of those things. Keying identity on the label means your customer record splits and merges according to mail routing decisions made by other companies.
The consequence is not a single dramatic failure. It is a steady divergence that nobody is assigned to notice.
Four failure modes of a two-row customer
| Failure mode | What the sync does | What the team sees |
|---|---|---|
| Shared mailbox | Three humans at one company all write from accounts@. Both systems store one contact. | Ticket history for three people under one name. A deal owner emails the wrong person. |
| Identity drift | Support edits the name to “Dana (Finance)” to tell two people apart. The CRM keeps “Dana Whitfield”. | Two spellings of one customer. Reports group by the wrong one. Neither is wrong enough to fix. |
| Merge orphaning | A rep merges two CRM contacts. The losing row’s id disappears. The help desk still points at it. | Tickets attached to a contact that no longer exists, or silently re-parented to whichever row the next sync matched. |
| Latency window | A contact is created in the CRM at 14:02. The sync runs hourly. | A ticket arriving at 14:09 creates a second contact, which the 15:00 sync then has to reconcile against the first. |
The last row is the one that undermines every “our sync is real-time” reassurance. Even a webhook-driven sync has a window, and windows are where duplicates are born. A nightly sync does not promise your systems agree; it promises they will agree tomorrow, and that promise is made by a process that is allowed to fail silently.
The concrete failure: a refund decided without the renewal
Here is the case that actually costs money, assembled from the mechanics above rather than from a story.
A customer’s card is charged twice. She writes to support from her personal address, because she is travelling and her work mail is on a laptop at the office. The help desk finds no contact matching that address and creates one: a new person, no company, no spend history, no account tier.
The agent reads a ticket from an apparently unknown individual asking for a refund of a few hundred currency units. Policy for unknown individuals is to route the request to finance and quote a ten-day window. The agent does exactly that, correctly.
In the CRM, the same human is the economic buyer on a renewal that closes in three weeks. Her company is a parent account with four subsidiaries rolling up beneath it. The account owner learns about the refund request when the renewal call goes badly.
Notice which part of this went wrong. Not the agent’s judgement, not the policy, not the model, not the sync, which performed its documented behaviour. The failure was architectural: the system that had the commercial context and the system that had the conversation were different systems, and the only bridge between them was a string the customer had chosen not to use that day.
A synced copy and a shared row are not the same thing
The distinction worth holding onto: a synced copy is a second row that aims to resemble the first, and a shared row is one row that several modules reference.
| Synced copy | Shared row | |
|---|---|---|
| Identity | Two ids, joined by email | One id, referenced by everything |
| Freshness | As fresh as the last successful sync | Always current, because there is nothing to refresh |
| Merge | Must be replayed into the other system | Happens once, and every reference follows |
| Failure mode | Silent divergence | Permission errors, which are loud |
| Operational cost | A reconciliation process forever | None |
The bottom row is the honest summary. Divergence is expensive precisely because it is quiet. A permission error stops someone and gets fixed within the hour; a contact that has been subtly wrong for eight months gets discovered in a QBR.
What a shared record has to own to be worth the name
Calling something a “single customer view” while it remains a dashboard that queries two systems misses the point. A record that genuinely resolves identity owns three things.
A stable internal id that nothing external can change. Tickets, deals, invoices and tasks reference the id, not the email address. Changing the address changes one field, not the identity.
Its own merge semantics. When two records turn out to be one person, the merge must state which value survives on each field and which linked records get re-pointed at the surviving row. Deals, tickets and invoices do not merge into each other; they move. Seeing the count of records about to move, before committing, is what makes a merge reviewable instead of regrettable.
Resolution at read time, not at write time. If the link between a ticket and a customer is stamped once when the ticket is created, a contact created five minutes later never connects. Resolving the link when the ticket is opened means the record catches up.
How Oneop handles this
In Oneop there is no help-desk-to-CRM integration, because there is nothing to connect. A ticket’s requester resolves to the same CRM contact row that the deal and the invoice reference, and a task can attach to a deal, a contact, a ticket, an invoice or eight other record types through one engine. No field mapping, no nightly reconciliation, and no window in which two systems disagree.
The help desk resolves the customer link when you open the ticket rather than storing it, so a contact created after the ticket arrived still connects. With an email address it matches CRM contacts and finance customers case-insensitively. Without one it will match on name only if exactly one record matches: it fetches two candidates and returns nothing when both come back, so two people sharing a name are never silently linked to the wrong account. No match returns an empty result and a one-click create, not a plausible-looking record.
Merging is where the shared row earns its keep. Contacts, companies and leads have a merge flow that renders the outcome first: which value survives on every field, which value is discarded, and which linked records will be re-pointed at the surviving row. Contacts, deals and activities also run a duplicate check while you type, so most duplicates are never created at all.
Two boundaries worth stating. The Companies module is opt-in, and while it is off, company merge endpoints return 404 and company records are not created automatically during lead conversion. And there is no ticket merge: two tickets about the same thing stay two tickets. What is enforced instead is inbound email dedup, scoped to your workspace, so the same message never opens a second ticket.
When two systems is still the right answer
If your operational truth is genuinely spread across best-of-breed tools that each do something no suite matches, a sync is the only architecture available, and you should run it deliberately: one system is the authority for contact identity, merges only ever happen there, and the sync runs one direction. Bidirectional sync between two systems that both allow merges is a distributed consensus problem that neither vendor has solved for you.
Oneop’s own HubSpot and Salesforce connectors sync bidirectionally for exactly one reason: a migration should run with both systems live rather than as a weekend cutover. That is a temporary state with an end date, not a target architecture.
The general principle is the same one that governs where an AI agent should run: the placement of data relative to the systems that act on it decides what can be guaranteed. Shrinking the number of systems of record beats hiring software to commute between them. If you are deciding how support and sales should share work day to day, the two-channel support operation argument covers the operational half of the same question.