The short answer
Before writing any integration, decide for each piece of data which system owns it. The CRM owns the opportunity, the ERP owns the order, accounting owns the invoice. Where two systems can both change the same field, you have a conflict problem rather than an integration problem.
Teams that skip this build a two-way sync, discover the systems disagree, and spend the next year reconciling by hand anyway.
Decide ownership per field
| Data | Usually owned by | Flows to |
|---|---|---|
| Company and contact details | CRM | ERP, accounting |
| Opportunity and pipeline | CRM | Reporting only |
| Order and delivery | ERP | CRM for visibility |
| Stock and availability | ERP | CRM, storefront |
| Invoice and payment status | Accounting | CRM, ERP |
| Credit limit | Accounting | ERP, CRM |
Once ownership is agreed, most fields become one-way, which removes conflict entirely. The remaining two-way cases are few enough to handle deliberately.
One way beats two way
Two-way synchronisation sounds better and is considerably harder. It needs conflict resolution, loop prevention and a way to decide which change wins when both sides edited the same record.
Most requirements described as two-way turn out to be one-way plus read access. Sales wants to see the invoice status, not edit it. Making that a read-only view removes a whole class of problems.
The matching problem
Every integration hits this: the same customer exists in three systems with three identifiers and slightly different names. Nothing works until they are linked.
- Find anything deterministic first: a registration number, an account reference that appears in both.
- Normalise names and addresses before comparing.
- Match probabilistically on what remains, and set a high bar for automatic acceptance.
- Route the uncertain matches to a person rather than guessing.
- Keep the mapping as a durable table, not as logic inside the sync.
That last point matters. A mapping table can be corrected by someone who knows the customers. Matching logic buried in code cannot.
Failure has to be visible
- Log every sync attempt, not just the failures
- Alert on a run that did not happen, not only on one that errored
- Put failed records in a queue someone owns and clears
- Make it possible to replay a failed record after the cause is fixed
- Report how many records are out of step, so drift is measurable
The second point is the one that catches people. A sync that silently stops running looks identical to a quiet day, and the gap is usually found weeks later by someone chasing a missing invoice.
Where custom is genuinely justified
If your three systems have supported connectors and your process is standard, a middleware product is usually cheaper and faster. Custom integration earns its cost where the mapping is specific to how you trade, where the matching is hard, or where the connector exists but does not cover the fields you actually need.
It is worth checking the off-the-shelf route properly before committing. Finding out afterwards that a connector would have done is an expensive lesson.