Most Salesforce to ERP integrations that fail do not fail on the API call. They fail because both systems think they own the customer, the price or the order, and nobody wrote down which one wins when they disagree.
What follows is the set of decisions we push clients to make before anyone builds a mapping, whether the ERP is SAP S/4HANA, Oracle Fusion Cloud ERP, NetSuite or Dynamics 365 Finance and Operations.
Decide the system of record per object, then per field
Start with a table that every stakeholder signs. A typical order-to-cash split looks like this:
| Data | Usual system of record | Direction | Notes |
|---|---|---|---|
| Prospect and account | Salesforce | Salesforce to ERP at first order | Do not create ERP customers for every lead |
| Customer financial data (credit limit, payment terms, tax) | ERP | ERP to Salesforce | Read-only in Salesforce |
| Item master (SKU, unit of measure, tax category) | ERP | ERP to Salesforce | Sales attributes may be enriched in Salesforce |
| Selling prices and discounts | Salesforce CPQ or Revenue Cloud, or ERP | One direction only | Never edited in both |
| Quote | Salesforce | Stays in Salesforce | |
| Sales order | Salesforce creates, ERP owns after acceptance | Both, with a status handshake | Lock in Salesforce after acceptance |
| Invoice and payment | ERP | ERP to Salesforce | Usually summary only |
Then go one level deeper. The account is Salesforce's, but the billing address on an existing customer may belong to the ERP once invoices have been issued against it. Field-level ownership written down early prevents the classic loop where each system overwrites the other's change every night.
Customers: create them late and match them carefully
Creating an ERP customer for every Salesforce account fills the ERP with prospects finance has to clean up. Create the customer when the first order is accepted, or at closed-won if the ERP needs it for credit checks.
ERPs also model customers differently. SAP uses business partners with roles and partner functions (sold-to, ship-to, bill-to, payer) under a sales area. NetSuite and Dynamics have their own parent and child structures. One Salesforce account may map to several ERP records. Decide whether Salesforce holds those as separate accounts, as related address records, or not at all, and store the ERP identifiers on Salesforce records as external ID fields.
Product and price master data
The ERP usually owns the item master: the SAP material, the NetSuite item, the Dynamics released product. Salesforce needs a copy for quoting, plus sales-only attributes such as product family, bundle structure and selling model.
Pricing is where integrations get political. If CPQ or Revenue Cloud calculates the price and the ERP recalculates it on the sales order with its own pricing conditions, you will eventually invoice a different amount from the quote. Pick one engine. If Salesforce prices the deal, the ERP should accept the net price on the order line. If the ERP is the price master, sync price lists into Salesforce price books with effective dates and currency, and keep discounting logic in one place.
Load master data with Bulk API 2.0 using upserts on external IDs, on a schedule that matches how often it changes. Deactivate retired products rather than deleting them, because quotes, orders and assets still point at them.
Orders and invoices: the flow that carries the money
The order is the handshake. A pattern that holds up:
- The Salesforce Order is activated, or the quote is accepted.
- An event carrying the order ID is published.
- Middleware builds the ERP sales order and returns the ERP order number, which is written back to Salesforce.
- The order is locked in Salesforce. Changes after acceptance go through an amendment that creates a new order, not edits to the original.
- The ERP sends status updates (credit hold, shipped, invoiced) and an invoice summary: number, date, amount, due date, balance.
Do not replicate every invoice line unless someone has a real use for it. Most sales and service users need to know whether the customer has paid, not the tax breakdown of line 14.
If you are moving from Salesforce CPQ to Revenue Cloud, this flow changes too, because orders and assets move to standard objects; our guide to how a CPQ to Revenue Cloud migration runs covers what that means for downstream systems.
Middleware or point to point
A direct Apex callout from Salesforce to the ERP, authenticated with a Named Credential, is reasonable for one ERP and two or three flows. It stops being reasonable when a third system appears, when transformations become complex, or when you need retries, monitoring and replay that nobody wants to build in Apex. Remember the Apex limits too: 100 callouts per transaction and a 120-second cumulative callout timeout.
MuleSoft Anypoint Platform is the obvious choice in a Salesforce estate, with connectors for SAP, NetSuite, Oracle and Dynamics. The API-led pattern is worth following: system APIs that wrap Salesforce and the ERP, a process API for order-to-cash, and experience APIs only where a channel needs one. When the ERP is replaced in five years, you rebuild one system API rather than every flow. Other integration platforms work too; what matters is that transformation, retry and monitoring live in one place.
Platform Events, Change Data Capture or batch
These are different tools, not three speeds of one tool.
| Mechanism | What it carries | Good for | Watch for |
|---|---|---|---|
| Platform Events | A payload you design | Business events like "order activated" | Choose Publish After Commit so events roll back with the transaction |
| Change Data Capture | Changed fields on a record, with a ChangeEventHeader | Replicating records to another system | Entity allocation limits; your own integration's writes come back as events |
| Bulk API 2.0 or scheduled batch | Large record sets | Master data, nightly reconciliation | Latency; locking during large loads |
| Synchronous REST callout | Request and response | Credit checks, stock availability | User waits; callout limits |
High-volume platform events and change events are retained for 72 hours, and every event carries a replay ID. Subscribers, typically through the Pub/Sub API, should store the last replay ID they processed so they can resume after an outage rather than miss events. With Change Data Capture, use the commitUser and changeOrigin fields in the header to filter out changes your own integration made, or you will build an echo loop.
Idempotency: assume every message arrives twice
Event delivery is at least once. Middleware retries. Users click the button again. Every write in the integration must be safe to repeat.
- Upsert on external IDs in Salesforce instead of insert.
- Give the ERP an idempotency key, such as the Salesforce Order ID plus a version number, and have it check before creating a sales order.
- In platform event triggers, throw
EventBus.RetryableExceptionfor transient failures so the platform redelivers, and make the handler safe to run again. - Never generate the business key in middleware at send time, because a retry will generate a new one.
Error handling and reconciliation
Separate technical errors from business errors. A timeout should retry automatically with backoff and land in a dead letter queue after the last attempt. A credit block or a missing tax code will never succeed on retry; it needs a person. Record both in an integration log object in Salesforce, linked to the record that failed, so a sales ops user can see why an order is stuck without opening the middleware console. Our Apex vs Flow guide covers where that error-handling logic should live.
Then reconcile, because silent failures are the expensive ones. A daily job should compare counts and totals by status in both systems: orders activated in Salesforce against sales orders created in the ERP, invoices issued against invoice summaries in Salesforce. Any mismatch becomes a task, not a surprise at month end.
Fixholics builds and supports Salesforce ERP integrations, including one for a B2B SaaS client where subscription billing and revenue recognition were integrated with Stripe and NetSuite, removing 90% of manual billing work. If your integration needs a design review or a rebuild, our integrations and MuleSoft team can start with the system of record decisions above.
Frequently asked questions
Who should own Salesforce ERP integration errors after go-live?
Split ownership by error type. Technical failures such as timeouts, expired credentials and middleware outages belong to the integration or platform team. Business rejections such as a credit block, a missing tax code or an invalid ship-to belong to a named person in sales operations or finance, with a queue they actually work every day.
Can Salesforce show ERP invoices without copying them into Salesforce?
Yes. Salesforce Connect can expose ERP data as external objects through an OData adapter or a custom adapter, so users see invoices on the account without the records being stored in Salesforce. It suits read-mostly data that users look up occasionally. If you need to report on invoice data at scale or drive automation from it, replicating a summary is usually the better choice.
How long does a Salesforce ERP integration project take?
It depends on how many objects flow in each direction, whether the ERP exposes usable APIs, how clean the customer and product master data is, and whether middleware already exists. Order-to-cash with customer, product, price, order and invoice flows is a substantial project. Agreeing system of record and data ownership first shortens it more than any tooling choice.

Partner