Bi-directional sync for finance data: how to avoid overwrites, and real-time vs batch
Two-way syncs overwrite each other without a system of record for each field. How to set ownership rules and choose real-time or batch for CRM-to-ERP data.
Part of the payment reconciliation and cash application guide.

Bi-directional syncs avoid overwriting each other's data by giving every field exactly one system of record and syncing it in one direction only. "Two-way sync" should mean two one-way flows over different fields, not two systems allowed to edit the same field. Add origin tagging so a sync never re-processes its own writes, and pick a cadence per field: near real-time for fields that block a customer-facing action, batch for everything else.
Most overwrite problems between a CRM and an ERP aren't bugs in the connector. They're an ownership decision nobody made. This guide gives you the field-ownership table that makes that decision explicit, the loop-prevention rules, and a way to choose real-time or batch per field.
Key takeaways
• One field, one owner. Every synced field has a single system of record; the other system gets a read-only copy.
• Two-way sync is two one-way flows. Customer name flows CRM → ERP; invoice status flows ERP → CRM. Neither system writes the other's fields.
• Tag the origin of every write. Salesforce Change Data Capture exposes a changeOrigin header specifically so an app can detect its own changes and avoid a cycle of updates.
• "Last write wins" is not a finance control. It hides conflicts instead of surfacing them; conflicts on owned fields should go to a person.
• Choose cadence per field, not per integration. Credit hold and invoice status often justify near real-time; price books and AR balances usually don't.
• Loopfour, the deterministic finance workflow automation platform, runs CRM-to-ERP sync on a field-ownership model with predefined direction and cadence, and holds conflicts for approval.
Why do bi-directional syncs overwrite each other?
Bi-directional syncs overwrite each other when both systems are allowed to edit the same field and the sync resolves the conflict by timestamp. Sales updates a billing address in the CRM. AR updates the same address in the ERP after a customer call. The next sync copies whichever changed last, and the other edit disappears.
Three patterns cause most overwrites:
| Pattern | What happens | Symptom |
|---|---|---|
| Shared ownership | Both systems can edit a field; the latest timestamp wins | Edits vanish; nobody knows which value is right |
| Echo loop | The sync writes to System B, B's change event triggers a write back to A | Records update repeatedly; API limits hit; audit logs flood |
| Stale batch | A batch job copies a snapshot taken before a newer edit | Old value overwrites a newer one on the next run |
All three disappear when each field has one owner and one direction. The echo loop also needs origin tagging, covered below.
The field-ownership table
The field-ownership table is the design document for any two-way sync: field → system of record → direction → cadence → conflict rule. Build it before configuring any connector. Here is a working version for a Salesforce and NetSuite setup; the same structure fits HubSpot, Attio, QuickBooks, or Sage Intacct.
| Field | System of record | Direction | Cadence | Conflict rule |
|---|---|---|---|---|
| Master customer ID | CRM (minted at account creation) | CRM → ERP, write once | On creation | Never overwritten; mismatch is an exception |
| Customer legal name | CRM | CRM → ERP | Near real-time on change | ERP edits blocked or flagged |
| Billing address and billing contact | ERP (AR team) | ERP → CRM | Hourly | CRM field read-only |
| Subsidiary and currency | ERP | ERP → CRM | On creation, then daily | Change requires approval |
| Payment terms | ERP | ERP → CRM | Daily | CRM field read-only |
| Credit hold status | ERP | ERP → CRM | Near real-time | CRM field read-only |
| Opportunity amount, close date, products | CRM | CRM → ERP, once, at closed-won | On closed-won | Post-sync edits go through change order, not sync |
| Sales order and invoice | ERP | ERP → CRM (status only) | Near real-time or hourly | CRM never edits |
| Invoice status and amount paid | ERP | ERP → CRM | Hourly | CRM never edits |
| Open AR balance | ERP | ERP → CRM | Daily | CRM never edits |
| Item and price book | ERP | ERP → CRM | Daily | CRM price changes go through approval |
| Account owner (sales rep) | CRM | CRM → ERP | Daily | Feeds commissions; ERP read-only |
Read the table by column, not by row. The Direction column shows that the "two-way sync" is really a set of one-way flows. The Conflict rule column shows that no field is resolved by timestamp. If a field needs to be edited in both places, that's a process problem to solve with a request workflow, not a sync setting.
Invoice status back to the CRM is often the first flow teams want. The specifics for NetSuite and Salesforce are in how to sync invoice and payment status from NetSuite back to Salesforce. If your systems don't share a customer key yet, fix that before anything else, using the approach in how to map fields between finance systems that don't share an identifier.
How to stop sync loops
Stop sync loops by tagging every write with its origin and discarding change events your own integration caused. Without this, a write to System B fires a change event, which the sync reads as a new change and writes back to System A.
Both major platforms give you what you need:
• Salesforce. Change Data Capture events carry a header with changeOrigin, changedFields, and commitTimestamp. Salesforce's ChangeEventHeader documentation says changeOrigin lets you detect whether your app initiated the change, so you don't process it again and avoid a deep cycle of changes. changedFields tells you which fields moved, so a sync only acts when an owned field changed.
• NetSuite. SuiteScript can read runtime.executionContext to tell a user interface edit from a web services, REST, or CSV import change. A user event script can skip outbound sync when the change came from the integration itself.
Add one more guard: compare before you write. If the target already holds the incoming value, skip the write. That single check removes much of the echo traffic even when origin tagging is imperfect.
The loop-safe flow:
Change event → read origin → discard if integration-originated → check changed fields against ownership table → compare to target value → write only owned fields that differ → log.
How often should CRM-to-ERP finance data sync: real-time or batch?
Sync in near real-time only when a stale value blocks an action or creates risk within minutes; sync everything else in batch. Real-time costs more in API calls, error handling, and monitoring. Batch is simpler to reconcile. Choose per field.
| Question | If yes | Example fields |
|---|---|---|
| Does a stale value let someone take a wrong customer-facing action? | Near real-time | Credit hold, invoice paid status before a collections call |
| Does a downstream process start from this change? | Event-driven | Closed-won creating a sales order |
| Is the value used for reporting or planning only? | Daily batch | AR balance, lifetime revenue |
| Does the value change rarely and centrally? | Daily or on change | Price books, payment terms |
| Does the source post in batches anyway? | Match the source cadence | Payments applied in a nightly cash application run |
A useful test: if a sales rep or collector saw the old value, would they do something wrong in the next hour? If yes, near real-time. If no, batch, and put the last-synced timestamp on the record so users know how fresh it is.
Batch also has a control advantage: a batch run is a natural unit for reconciliation. You can count records in, records written, and exceptions, and compare. Event-driven syncs need the same counts, rolled up by hour or day, or silent gaps go unnoticed.
Book a workflow review to see your own CRM-to-ERP fields mapped into an ownership table with direction and cadence set per field.
What about conflicts that still happen?
Route them to a person, with both values and both timestamps, and record the decision. Even with clean ownership, conflicts arise: someone edits a read-only field through an admin permission, a data import bypasses the rules, or two records merge.
• Detect. Before writing, compare the target's current value to the last value the sync wrote. If they differ, someone changed the target outside the sync.
• Hold. Don't overwrite. Create an exception with the source value, the target value, who changed each, and when.
• Decide. A named owner picks the correct value in Slack, and the sync applies it.
• Record. The decision and the approver go into the run's log.
That is slower than last-write-wins by design. For finance fields, a visible conflict is cheaper than an invisible overwrite discovered at audit.
How to choose a tool for bi-directional finance sync
Choose a tool that lets you encode field ownership, direction, cadence, and conflict rules explicitly, and that shows you what it did on every run. Most integration platforms can move fields both ways. The difference is whether they make you decide ownership or let you skip it.
Horizontal iPaaS tools and packaged connectors are flexible and fast to deploy, and for teams with integration engineers they're a strong option; we compare three of them in Celigo vs Boomi vs Workato for NetSuite-Salesforce finance sync. Their default conflict behavior is often timestamp-based, and changing that is your build to maintain.
Loopfour takes a narrower position. The field-ownership table is the configuration: each field's owner, direction, and cadence are set in advance, integration-originated changes are discarded, and conflicts stop and ask a named approver. We build it on your existing Salesforce, HubSpot, NetSuite, QuickBooks, or Sage Intacct accounts and maintain it when either side changes. Every run leaves an execution tree showing which fields moved, in which direction, and who resolved each conflict.
Frequently asked questions
How do bi-directional syncs avoid overwriting each other's data? They assign each field a single system of record and sync it in one direction only, so both systems never edit the same field. They also tag writes by origin to ignore their own changes, and route any true conflict to a person instead of resolving it by timestamp.
How often should CRM-to-ERP finance data sync? Sync fields in near real-time when a stale value would cause a wrong action within the hour, such as credit hold or invoice paid status. Sync reporting fields such as AR balances, price books, and payment terms in daily batches, and show users the last-synced time.
What is a field-ownership table? A field-ownership table lists every synced field with its system of record, sync direction, cadence, and conflict rule. It's the design document for a two-way integration and should be agreed by finance, sales operations, and whoever maintains the sync.
Is last-write-wins acceptable for finance data? Not for fields that affect invoicing, revenue, or collections. Last-write-wins silently discards one edit; for finance data, conflicts should be held and resolved by a named owner with the decision logged.
How do I stop an infinite sync loop between Salesforce and NetSuite? Discard change events your integration caused, using Salesforce's changeOrigin header and NetSuite's execution context. Also compare the incoming value to the target before writing, and skip the write if nothing changed.
Should the CRM or the ERP own the customer record? Usually the CRM creates the customer and owns sales-facing fields such as name and account owner, while the ERP owns billing address, subsidiary, payment terms, and credit status. Whatever split you choose, write it down per field.
Conclusion
Bi-directional sync for finance data works when each field has one owner, one direction, and a cadence that matches how the value is used. Tag origins to stop loops, compare before writing, and send real conflicts to a person. Build the field-ownership table first, and the connector becomes a much smaller decision.
Tell us the one workflow your team dreads. We will show it running — deterministic, permissioned, and auditable.
Book a demo.
Sources
• Salesforce: ChangeEventHeader fields (Change Data Capture)
• Oracle NetSuite: runtime.executionContext
Related reading
• How to sync invoice and payment status from NetSuite back to Salesforce
• How to map fields between finance systems that don't share an identifier
• Celigo vs Boomi vs Workato for NetSuite-Salesforce finance sync
• How to integrate NetSuite with Salesforce for finance teams
• Salesforce-to-NetSuite sync errors: invalid subsidiary references and missing price books
Sources
- Salesforce, Change Data Capture Event Fields: Header (opens in a new tab).
- Oracle NetSuite, NetSuite Help Center (opens in a new tab).
