Workflow AutomationOperationsn8nZapier

Two-way sync keeps overwriting your good data

A two-way sync overwrites good data because the sync's own write updates the last-modified timestamp it uses to decide who wins, so the receiving system looks newer on the next pass. The fix is to assign every synced field to one owning system and run two one-way syncs over field sets that do not overlap, instead of one bidirectional sync with a conflict rule.

Alexey YushkinFounder, GENERAL INFORMATICS2 min read

A two-way sync overwrites the value you just corrected because the rule most syncs use to break ties, the most recent change wins, reads a timestamp that the sync itself sets. When system A pushes a value into system B, B's record becomes the most recently modified one. On the next comparison B looks newer, so B's copy wins and pushes back. The fix is not a better conflict rule. It is to stop building a two-way sync and build two one-way syncs over field sets that do not overlap.

Why a two-way sync overwrites the value you just fixed

Three failure shapes, one root cause: both systems are allowed to write the same field.

The echo is the loudest one. A rep fixes a phone number in the CRM. A webhook fires. The workflow writes the number into the billing system. Billing's own webhook fires, because a record changed. The workflow writes back to the CRM. Every hop is a legitimate record change as far as each platform can tell, because neither one knows the change came from your workflow. On a good day it settles after two hops. On a bad day the two flows keep firing each other until a rate limit stops them, and your execution history shows four hundred runs on one contact.

The blank overwrite is the one that generates a support ticket, because a human watched a value they typed disappear. System B has the field empty. The sync decides B is newer and copies the blank over A's populated value. HubSpot's own Data Sync guards against this specific case: its documentation states that if there is no field value in the default app, no data will be changed in the third-party app. That guard exists because the problem is common, not because it is exotic.

The stale overwrite is the quietest. A nightly job in one system touches every record: a lead score recalculation, a rollup, an enrichment refresh. Every record in that system now looks recently modified. The next sync pass hands that system the win for everything, including fields no human has edited in months.

Last-write-wins cannot arbitrate a two-way sync

Every guide on the topic lists the same three conflict strategies: last write wins, a priority system, and field-level rules. The first gets listed first because it sounds neutral and requires no decisions. It is mechanically unusable, because the timestamp you are comparing is written by the process you are trying to arbitrate.

To use a timestamp honestly you need one that records when a person changed the field and that your sync does not touch. The platforms differ on whether such a value exists, and the differences decide the build.

Salesforce carries two timestamps on every record. LastModifiedDate tracks user-initiated changes. SystemModstamp also captures modifications from automated internal processes, and Salesforce documents it as always equal to or more recent than LastModifiedDate. Salesforce recommends SystemModstamp for integrations that detect changes, since it will not miss records. That recommendation is right for pulling changes and wrong for resolving them. Arbitrate on SystemModstamp and a rollup recalculation outranks a human edit made ten minutes earlier in the other system. Pull on one field, arbitrate on the other, and be deliberate about which is which.

Airtable is better here than its reputation suggests. A Last Modified Time field can be scoped to specific fields rather than the whole record, so you can build a timestamp that only moves when the fields you actually sync move. Airtable also shows "Automations" as the collaborator in Last Modified By when the change came from an Airtable automation, which is an origin marker you can filter on.

HubSpot gives you the cleanest marker of the three. Every property-change webhook payload includes a changeSource field, documented as the source of the change, using the same set of values that appears in property history. Filter API and integration sources out at the top of the workflow and the echo never starts.

Map ownership by field, not by system

This is where the vendor guides get it half right. They tell you to build a field-ownership matrix, which is correct. Then they hand you a tool whose conflict setting is one dropdown that picks a single app to win for the entire object. HubSpot Data Sync works exactly this way: you choose a default app that overwrites the other when values disagree. That is a per-object decision. Your ownership map is per-field. The two shapes do not fit, which is why the matrix everyone recommends usually ends up in a document nobody implements.

Write the map anyway. It takes thirty minutes and it is the artifact the build comes from. For a typical CRM, billing, and support-desk stack:

FieldOwnerDirectionWhat breaks if you reverse it
Legal entity name, billing addressBillingBilling to CRM, one wayFinance corrects the entity name for an invoice and a rep's typo overwrites it before the next billing run
Contact email, phoneCRMCRM to billing and supportBilling keeps the accounts-payable address and your sales email goes to a shared AP inbox
Plan, seat count, MRRBillingBilling to CRM, one wayA rep edits MRR to match a verbal quote and your pipeline number stops matching what you invoice
Payment status, past due flagBillingBilling to CRM and supportSupport sees a current account and waives a fee for a customer who has not paid in ninety days
Lifecycle stage, account ownerCRMCRM to support and billingRound-robin assignment in the help desk fights the CRM owner field and the account changes hands weekly
Last human contact dateSupportSupport to CRM, one wayThe CRM overwrites it with a marketing send and your "no contact in 90 days" report returns nothing

The rule for filling it in: ownership goes to the system where a person changes the value on purpose, not the system that displays it most often. Support staff look at plan and payment status all day and own neither.

Build two one-way syncs instead

Once every field has one owner, the bidirectional sync stops existing. You have a set of fields flowing A to B and a disjoint set flowing B to A. Two workflows that cannot conflict, because no field appears in both.

Three rules make that hold up in production.

Compare before you write. Read the current value at the destination, compare it, and skip the write when it already matches. That one check ends echo loops without any vendor-specific setting, and it stops the timestamp churn that poisons every other rule you might add later. Most operators build "on update, write" and never add the equality check, which is the entire reason the loop exists.

Filter by origin at the trigger, not halfway down the flow. Use whatever marker the platform gives you: changeSource on HubSpot webhooks, LastModifiedById compared against your integration user in Salesforce, Last Modified By in Airtable. When a platform gives you nothing, keep a shadow field on the record holding a hash of the last value your sync wrote, and skip when the incoming value hashes to the same thing.

Write fields, not records. A generic "update record" call sends every field in your payload, including the ones you never meant to touch, which is how a one-way sync quietly becomes bidirectional again. Build the update body explicitly with only the fields that side owns. In n8n that means a Set node listing the fields, not passing the whole item through.

There is one case where genuinely bidirectional behavior is the requirement: a field both teams edit in both places by design, usually a free-text notes field. Do not sync it. Keep it in one system and link to it from the other. Every hour spent making that field sync correctly is an hour that produces a worse outcome than a link.

Where to start

Open a sheet, list every field that currently moves between your two systems, and write one system's name beside each one. The fields where you hesitate are the finding. Those are the fields your current sync is already deciding by accident, and they are the ones producing the overwrites people complain about. Assign each one an owner, then delete the bidirectional connection and rebuild it as two one-way flows over the split.

We start every workflow automation engagement this way when a CRM and an operational system already talk to each other, because the map usually finds two or three fields nobody realized were contested. When lead and account data is the contested set, the ownership question is really about which system feeds the rest, which is a customer acquisition design decision before it is an automation one. If the map comes back with more than a dozen contested fields, neither system is currently the data of record, and picking one is the project. Tell us which two systems you are trying to reconcile and we will tell you which one should win.

Frequently Asked Questions

SOURCES & CITATIONS

  1. Connect and use HubSpot data sync HubSpot Knowledge Basehttps://knowledge.hubspot.com/integrations/connect-and-use-hubspot-data-sync
  2. Difference between SystemModstamp and LastModifiedDate Salesforce Helphttps://help.salesforce.com/s/articleView?id=000387261&language=en_US&type=1
  3. Webhooks v3 API guide HubSpot Developershttps://developers.hubspot.com/docs/api-reference/legacy/webhooks/guide
  4. Last modified time field Airtable Supporthttps://support.airtable.com/docs/last-modified-time-field

About Alexey Yushkin

Alexey is the founder of GENERAL INFORMATICS LLC. He designs and ships AI and automation systems for businesses and operators across the US.

Connect on LinkedIn

Related reading

Want this kind of system in your business?

We build practical AI and automation systems for operators. Send us your current workflow and we will show you what to automate first.

Request a Workflow Review