Two arrows hide a state machine

Architecture diagrams often show Product A ↔ Provider B. That notation is useful, but dangerously incomplete. Each side can change independently, both APIs can fail between reads and writes, and the same logical entity can have different identifiers and field semantics in each system.

The integration therefore needs durable knowledge outside either provider. At minimum it must know which records correspond, which versions it has observed, which write it last initiated and where incremental reading should resume. Without that state, the connector cannot reliably tell a new edit from its own echo.

Synchronization is a protocol over time. The hard part is not moving JSON. It is preserving intent across independent systems that can change and fail at different moments.

Persistent state is the integration

For a contact flowing between a SaaS product and an external provider, a useful link record might keep the product ID, provider ID, last-seen versions, checkpoint, tombstone state and a fingerprint of the last outbound payload. These are operational facts, not business fields.

Keeping them makes important decisions possible:

A stateless webhook handler can appear to work in a demo. It fails when messages arrive out of order, delivery is repeated or a process stops after the provider accepted a write but before local state was committed.

Echo suppression is not a boolean

Suppose the product changes a phone number. The connector writes it to the provider. The provider emits a change event. A naïve connector reads that event as new intent and writes it back to the product, which may emit another event. The systems can oscillate even when the value appears identical because normalization, timestamps or formatting differ.

Reliable echo suppression compares evidence: source versions, write fingerprints, causal metadata where available and normalized field values. It also expires that evidence carefully. A permanent “ignore the next event” flag breaks when unrelated user edits arrive before the expected echo.

Conflicts are product policy

Two-way sync eventually sees both sides change before either change has been fully propagated. “Last write wins” sounds objective, but clocks differ and delivery latency can make the observed order misleading. Field-level merging sounds safer, but it can combine changes into a state no user ever intended.

The right policy depends on ownership. A product may be authoritative for lifecycle state while the CRM owns sales notes. Some fields may allow provider edits; others may be outbound only. The canonical model should encode these decisions explicitly instead of hiding them inside connector-specific code.

Every mapping loses something

A Google Workspace shared contact and a HubSpot contact both look like “Contact,” but their worldviews differ. One is an address-book entry shared across a domain. The other participates in CRM properties, ownership and lifecycle. Even similarly named fields can have different constraints or multiplicity.

A canonical model should be intentionally smaller than the union of every provider schema. It represents the stable product contract. Provider adapters then preserve external identifiers and provider-only metadata separately, map unsupported values transparently and expose loss rather than inventing false equivalence.

If a provider supports three email labels and the product supports one email string, that is not a serialization detail. It is a product decision about precedence and data loss.

Retries must be idempotent

Networks fail ambiguously. A request can time out after the provider applied it. Retrying a create may then produce a duplicate; not retrying may lose the change. A safe worker uses idempotency keys where a provider supports them, searches by stable external references where it does not, and records attempts with enough context to reconcile ambiguous outcomes.

Backoff handles transient errors, but it is not the complete strategy. The job also needs an attempt limit, a review path for permanent failures and checkpoint advancement rules that do not skip failed work. Rate limits belong in the same design because an aggressive retry loop can amplify an outage.

Deletion is a distributed decision

“Deleted” might mean hard-deleted, archived, moved out of a visible scope, permission-revoked or merged into another record. Propagating every disappearance as a deletion risks destroying valid customer data. Ignoring all deletions creates records that never converge.

Connectors need explicit tombstones, provider-specific disappearance rules and often a grace or reconciliation pass before destructive propagation. Which system is allowed to delete which fields or entities should be agreed during integration design.

Auditability turns incidents into answers

When a customer asks why a contact reverted, “the webhook ran” is not an answer. An inspectable sync event should connect the source observation, normalized change, policy decision, provider write, response and resulting link-state update.

This does not require storing every sensitive payload forever. It requires durable identifiers, redacted diffs, timestamps, attempts and causality. Good audit records make support faster and reveal repeated connector behavior that deserves a product-level fix.

The pattern extends beyond contacts

Calendars add recurrence and timezone semantics. CRMs add ownership and custom fields. Directories add identity lifecycle. Accounting systems add irreversible postings. The domain rules change, but the integration machinery repeats: canonical contracts, durable links, checkpoints, idempotent work, conflict policy and auditability.

That repetition is the infrastructure opportunity. A B2B SaaS team should define the product contract and customer experience. It should not have to rebuild the same operational machinery for every provider indefinitely.

Have a real integration requirement? Tell SyncMachina what should sync with what →