← All documentation

Bidirectional sync and conflict resolution

A bidirectional connector moves data both ways: your source system writes into HubSpot, and changes made by your team inside HubSpot flow back out. When the same field on the same record is touched from both sides close together in time, something has to decide who wins. This page explains how Plugsync resolves that, exactly, and where the honest limits are.

Avoid the conflict first: field ownership

The cheapest way to resolve a conflict is to make it structurally impossible. If a field is only ever supposed to be edited on one side (say, internal_notes is CRM-only, vat_number is ERP-only), restrict the inbound write to the fields your source actually owns using the entity’s fields allowlist. A field that’s never written by the inbound path can never be overwritten by it, regardless of which conflict strategy is active downstream.

The other structural mitigation is sending deltas, not snapshots. If your source system posts “what changed” instead of “the full current state” on every event, there’s nothing to fight over for the fields it didn’t touch. Snapshot-style payloads (common with older ERPs that re-send the whole record on a timer) are the case where the strategies below matter most, and where their limits (see below) are most visible.

Conflict resolution is the safety net for the fields that are genuinely editable from both sides. Use it for those, not as a substitute for field ownership.

The three strategies

Conflict resolution is per-property, not per-record: on the same sync event, one field can be kept from HubSpot while another is written from the source, depending on what each field’s own history says.

source_wins (default)

The inbound write always applies. This is the pre-existing behavior and the default when no conflicts block is configured: zero extra API calls, zero behavior change for connectors that don’t need this.

target_wins

Stateless rule: a field whose most recent HubSpot value was written by someone or something other than Plugsync stays as-is in HubSpot, no matter when either side touched it. Once the field belongs to a CRM edit, the inbound write for that field is dropped on every subsequent sync, until HubSpot’s own record shows Plugsync as the last writer again (for example, after a sync that wasn’t in conflict).

most_recent

Per-field timestamp comparison: the HubSpot value wins only if it is strictly newer than the source’s edit time for that event. Otherwise the inbound value is written. The source edit time is resolved through a fallback chain:

  1. conflicts.source_modified_path, if configured (a JSON path into your event payload, e.g. an ERP’s own “last modified” field);
  2. the event’s occurred_at (from the Event API timestamp or the HubSpot journal), if the path above isn’t configured or doesn’t resolve;
  3. the time Plugsync ingested the event, as a last resort.

In both target_wins and most_recent, every field identified as contested is recorded as a resolved conflict, whichever side won.

Configuring it

Conflict resolution is a top-level block in the connector config, alongside entities and flows:

{
  "version": "3.1",
  "conflicts": {
    "strategy": "most_recent",
    "source_modified_path": "$.payload.updated_at"
  }
}

The block goes through the normal draft, review, and publish flow, same as any other config change.

Seeing what got resolved

Every field the detection identifies as contested is persisted as a resolved conflict, visible in the connector’s Conflicts tab: which field, the value on each side, which strategy resolved it, and which value was kept. Nothing is silently dropped without a record.

The honest limits

What’s next