Skip to main content
All guides

Integration & automation

CRM to ERP integration: where it goes wrong

Connecting a CRM to an ERP is rarely a transport problem. It fails on ownership, identity, and money, in the same seven places every time.

Updated December 22, 2025 · 9 min read

The short answer

  • Assign one system of record per entity before writing code: the CRM owns the opportunity, the ERP owns the customer master, product, price, and order. Answering "both" causes every downstream problem.
  • Most sync failures are identity failures, not transport failures. Match on a stable external ID written back into both systems. Email, company name, and address are not keys.
  • Quote-to-cash breaks when the CRM price book and the ERP item master drift apart. Publish the catalogue from the ERP on a schedule and treat CRM-side edits as a defect.
  • Currency, tax, and rounding mismatches produce invoices that are pennies wrong and therefore unusable. Agree line-level rounding order and one tax authority before the first order syncs.
  • Every integration needs defined behavior for a rejected record: a named owner, a visible queue, idempotent retries, and an error message a salesperson can act on without opening a ticket.

Connecting a CRM to an ERP is usually sold as plumbing. Both systems have documented APIs, both have a supported connector, and the demo moves an account across in ninety seconds. Then the project takes nine months. The reason is that the hard part was never transport. It was deciding who is allowed to be right about a customer, a price, and an order, and what happens when the two systems disagree at 4pm on the last day of the quarter.

Quote-to-cash is the canonical example. A salesperson builds a quote in the CRM, it becomes an order, the ERP fulfils and invoices it, and the payment status has to come back so the account team knows where they stand. That single loop crosses every failure point below. The patterns here apply to any CRM-to-ERP pairing; the platform names change and the problems do not.

Decide the system of record, one entity at a time

The first design question is which system owns each entity. The answer that causes the most damage is "both", usually arrived at because sales want to edit customer records without waiting on finance, and finance will not accept a customer master they do not control. Bidirectional ownership means you now need conflict resolution, and every conflict resolution rule you write is a rule someone will be surprised by later.

Assign ownership per entity, write it down, and enforce it in the UI. The system that does not own an entity should display it read-only, with a visible source label. This is a change management problem more than a technical one, and it is cheaper to fight it in week two than in month eight.

EntitySystem of recordCopy lives inRationale
Lead / prospectCRMNowhereNot a real entity to finance yet. Do not sync it; you will create thousands of junk customer records.
Customer / account masterERPCRM (read-only)Billing address, tax registration, credit terms, and legal entity are financial facts with audit consequences.
ContactCRMERP (billing contacts only)Sales relationships change constantly; the ERP only needs the people who receive invoices.
Product / itemERPCRM (read-only)An item that cannot be fulfilled or costed should never be quotable.
Price / price bookERPCRM (read-only, versioned)Prices carry margin and contract obligations. Discounts belong in the CRM as a modifier, not as a new price.
Opportunity / quoteCRMNowherePipeline is a sales artefact. The ERP does not need to know about deals that may not happen.
Sales orderERPCRM (status only)Once accepted, the order has inventory, revenue, and tax consequences. One writer only.
Invoice / payment statusERPCRM (read-only)Sales need visibility to avoid selling to an account on credit hold. They do not need write access.
A defensible default assignment for quote-to-cash

Identity is the hard part, and email is not a key

Nearly every integration that goes badly goes badly here. The two systems were populated independently for years, so the same customer exists as "Acme Corp", "Acme Corporation", and "ACME Corp - DO NOT USE". Matching heuristics get proposed early because they appear to work on a sample. They fail in production at a rate of a few percent, which is enough to poison a customer master permanently.

  • Email is not a key. People change jobs, share inboxes, and use info@ addresses across multiple legal entities.
  • Company name is not a key. Punctuation, legal suffixes, and trading names differ between the system sales use and the system finance use.
  • Address is not a key. Two subsidiaries can share a headquarters and require separate invoices and separate tax treatment.
  • Tax IDs are close to a key, but they are optional in most CRMs and blank on the records that matter most.

The workable answer is an explicit external ID. When a record is created in its system of record, write the counterpart's identifier back into a dedicated field on both sides, and make that field the only thing the integration matches on. Everything else is a hint. For the existing back catalogue, run a one-time reconciliation with human review of anything below a high confidence threshold. Accept that this is a project with a budget, not a script someone runs on a Friday.

The product and price catalogue drifts apart

On day one, the CRM price book and the ERP item master are identical because someone exported one into the other. Drift starts immediately. A product is discontinued in the ERP and remains quotable in the CRM. A price rise goes into the ERP effective the first of the month and the CRM keeps the old figure. Someone adds a one-off SKU to close a deal, and it has no cost, no tax code, and no fulfilment path.

The symptom is orders that reject at the boundary, or worse, orders that succeed at the wrong price and are caught during the close. Publish the catalogue in one direction, on a schedule, and make CRM-side product creation a permission nobody has. Where sales genuinely need flexibility, give them a discount or an override field on the line, a modifier the ERP can interpret, rather than the ability to invent a price.

  • Sync effective dates, not just current values, so a quote built today for a January start uses January pricing.
  • Carry the ERP's active/inactive flag and hide inactive items from quoting rather than deleting them, so historical quotes still render.
  • Version price books. A quote must record the price book version it was built against, or you cannot explain a two-month-old quote to a customer.
  • Include unit of measure and any conversion factor. Quoting in cases and fulfilling in eaches is a reliable way to ship ten times too much.

Currency, tax, and rounding will not agree by default

Two systems that both handle money will handle it differently. The CRM typically rounds the line total after applying a discount percentage; the ERP typically rounds each unit price first and multiplies. On one line the difference is a cent. Across a fifty-line order it is enough that the invoice does not match the quote the customer signed, which turns into a collections conversation.

Currency adds a second axis. The CRM applies a rate from the date the opportunity was created; the ERP applies a rate from the date of invoice. Both are defensible and they produce different numbers. Decide which date governs, and store the rate used on the record so the difference is explainable rather than mysterious.

Tax is the one to give up on early. Let one system be the tax authority, almost always the ERP or the tax engine behind it, and have the CRM display an estimate clearly labelled as an estimate. A CRM that calculates its own tax will be wrong on exemptions, nexus, and jurisdiction boundaries, and finance will not accept the output.

Sandbox parity, and why people end up testing in production

Teams test in production because the sandboxes make testing impossible, not because they are careless. The CRM sandbox refreshes on a different cadence than the ERP sandbox, so the two contain different customers. Sandbox refreshes wipe the external ID fields the integration depends on. Third-party tax and payment services often have no sandbox at all, or one that returns fabricated values. So a developer verifies one behavior against real data "just once", and that becomes the process.

  1. 1

    Refresh both sandboxes together, from the same point in time, and treat a refresh as a coordinated event rather than an individual action.

  2. 2

    Script the post-refresh setup: restore integration credentials, endpoints, external ID mappings, and the seed data set. If this is a manual checklist, it will be skipped.

  3. 3

    Maintain a small named fixture set (a domestic customer, a foreign-currency customer, a tax-exempt customer, an account on credit hold, a multi-currency multi-line order) and run every release against all of them.

  4. 4

    Stub external tax and payment services with recorded responses so the test result does not depend on a third party's sandbox being up.

  5. 5

    Give the integration a dry-run mode that validates and reports without writing, so production behavior can be checked safely when it genuinely must be.

Sync timing against the finance close

Near-real-time sync sounds like the obvious goal and is often the wrong one. During the last two days of a period, finance are reconciling, and an integration that keeps pushing backdated orders into a closing period creates work rather than removing it. Equally, a nightly batch means a salesperson who submits an order at 9am has no order number until the next morning, so they phone operations, who create it by hand, and now you have duplicates.

Separate the flows by urgency rather than syncing everything on one schedule. Orders and customer creation should move within minutes because a person is waiting. Payment status, credit holds, and fulfilment updates can run on a schedule. Anything that posts to a period should respect a close calendar, with a documented rule for what happens to records that arrive after the cutoff: usually held in a queue and posted to the next period, never silently dated backwards.

The rebuilt platform connected accounting, communications, and fleet systems through clean APIs, so job completion and invoicing moved on the same day instead of waiting on a manual handoff.
From ERP to agility, an anonymized B-Team engagement

When a record is rejected and a salesperson is waiting

This is the case most integrations handle worst. An order is rejected halfway through (the customer is on credit hold, a SKU is inactive, a required field is empty), and the failure lands in a log nobody reads. The salesperson sees a spinner, then nothing. They re-submit. Now there are two partial orders, one of which may have consumed inventory.

Design the failure path with the same care as the success path. Every write must be idempotent, keyed on a request identifier generated by the sending system, so a retry cannot duplicate. Every rejection needs a class, an owner, and a message written for the person who can fix it rather than for the developer who wrote the handler.

RejectionWho resolves itWhat the integration should do
Customer on credit holdFinanceHold the order, notify finance and the owning rep, show the reason in the CRM. Do not retry, because the condition will not clear on its own.
Inactive or unknown itemProduct or operationsReject the whole order rather than part of it, name the offending line, and block that item from further quoting.
Missing required fieldThe salespersonReturn the field name in plain language and deep-link back to the record. Ideally validate before submission so this never reaches the ERP.
Price or total mismatchIntegration ownerFail loudly and stop. Never accept the ERP total silently over a signed quote total; that difference is a commercial dispute.
Timeout or transport errorNobody, initiallyRetry with backoff using the original request identifier, then escalate to a monitored queue after a bounded number of attempts.
Duplicate customer matchData ownerRoute to a review queue with both candidate records shown. Do not auto-merge and do not auto-create.
Rejection classes and the response each one needs

The queue is the part that gets skipped. A failed record with no owner and no visible home becomes a permanent silent gap between the two systems, and the gap is usually found during an audit. Give the queue a screen, an assignee, and an age alert. If nothing is looking at failures within an hour, the integration is not in production. It is in trial.

Common questions

Should we use a prebuilt connector or build the integration ourselves?
A prebuilt connector handles transport, authentication, and retries, which is real work you should not repeat. It does not decide your system of record, your matching rules, or your rounding order, and those are where the cost is. Expect a connector to cover the mechanics and expect to spend most of the budget on the business rules regardless of the tool.
Can the CRM and the ERP both own the customer record?
Technically yes, and it is the most common source of long-running data problems. If you must split ownership, split it by field rather than by record (sales own relationship fields, finance own billing, tax, and credit fields) and enforce that split in both interfaces. Avoid last-write-wins, because it lets a stale copy overwrite a corrected financial detail with no trace.
How long does a CRM-to-ERP integration take?
The first working flow usually appears within a few weeks. Getting to a state finance will rely on takes longer, and the schedule is driven by data cleanup and decision-making rather than development. If the existing customer masters have never been reconciled, treat that reconciliation as its own workstream with its own budget.
What should sync in real time and what can run in a batch?
Anything a person is waiting on should be near-real-time: customer creation, order submission, and the order number coming back. Payment status, credit holds, fulfilment updates, and reporting extracts are fine on a schedule. Anything that posts into an accounting period should also respect the close calendar and queue rather than backdate.
How do we avoid creating duplicate customers during the first sync?
Disable automatic creation for the initial run and send every unmatched record to a review queue. Match only on an explicit external ID that you write back into both systems, and treat name, email, and address as hints for human reviewers rather than as keys. Enable auto-create only after the match rate has held steady through a full billing cycle.
What is the single most useful thing to do before the project starts?
Write down the system of record for each entity and get finance and sales leadership to agree to it in the same room. Most integration escalations later in the project are re-litigations of that one decision. Having it documented converts a political argument into a change request.

Want this applied to your operation?

The first conversation is a no-cost fit discussion about the problem, its importance, and the people involved. We respond within one business day.

Start a conversation