CRM Integration
Rox ↔ CRM integration: Mappings, Real-time & Batch write-back, Bulk sync, ID mapping, and Audit logs — always-current truth view
Rox relies on its System of Record (SOR) — a unified knowledge graph — to interface between our agents and a customer’s public and private data. In practice, the most business-critical private data already lives in the customer’s CRM. Because we meet customers where they are, we integrate deeply with their CRM and build our SOR by performing entity resolution across both public and private sources.
Rox treats any CRM as just another data source feeding the SOR. Today we support Salesforce and HubSpot; the platform is designed to extend to other CRMs with configuration.
Why We’re Doing This
Rox is taking over revenue operations for Global 2000 enterprises. To earn that right, we meet customers in their existing stack—CRMs synced to a warehouse of choice—and we write back anything enriched or edited in Rox to their CRM. We’re confident because the value our agents deliver is immediate and compounding. As that value becomes obvious, customers will graduate to a warehouse-native future where Rox writes directly to their warehouse (and let’s be honest: in that future, Rox is the CRM).
Requirements
We support two essential capabilities:
Ingest → SOR. Bring data from the external CRM into Rox’s SOR.
Write-back → CRM. Persist enriched/edited data from Rox to the customer’s CRM (which, at large enterprises, is also mirrored into their data warehouse).
Challenges (What Makes This Non-Trivial)
Initial ingestion resolution. Reconcile CRM data with existing public + private sources in Rox (including Rox-native entities).
Agentic CRM behavior. Users must be able to:
Edit data in Rox and have it bi-directionally synced to the CRM.
Write back agent-generated enrichments so data stays consistent without users babysitting their legacy CRM.
Change-origin disambiguation. When CRM changes come back through ingestion, we must deterministically identify whether each change originated in the CRM or in Rox (and was written back) for resolving duplicates across both data systems.
Always-current “latest state.” Sellers and agents should see the most recent, accurate value across Rox and the CRM without any manual refresh.
Read-only insights, written back. Customers often want Rox read-only columns (e.g., Clever Columns, firmographics) materialized into CRM fields for warehouse consistency—requiring reliable bulk write-backs.
CRM-agnostic mapping. Provide a mapping layer so admins can map Rox fields to CRM fields with uni- or bi-directional sync semantics.
How We Do It
See our SOR overview for ingestion fundamentals and graph construction; this section focuses on how write-backs and sync semantics integrate with that layer.
We organize the integration in three layers:
Mappings Layer — field mapping + validation + sync direction.
Real-Time Layer — transactional write-back for user edits, with automatic latest-state resolution.
Batch Layer — high-volume, uni-directional write-backs (e.g., Clever Columns) and safe reconciliation of pending real-time writes.

1) Mappings Layer
Admins configure mappings from Rox fields → CRM fields (or create an equivalent Rox field to mirror an existing CRM field). Each mapped field carries:
Sync Direction:
Read-only in Rox → CRM (uni-directional; Rox is source of truth)
Bi-directional (edits in Rox and CRM propagate both ways)
Validation Rules (type-aware): Some examples include (extending to more complex rules in the future)
NUMBER
: min/max;TEXT
: max length;nullable
: allowed or not.
When users update mapped fields, Rox enforces these validations for any update intended to persist both in Rox and the CRM.
2) Real-Time Layer (Automatic Latest-State)
The clock problem. CRM and Rox clocks are not guaranteed to be synchronized, so raw timestamps aren’t a safe basis for “who wins.” We avoid NTP-style clock alignment and instead implement an optimistic concurrency strategy that does not depend on wall clocks.
Pre-write latest-state check (automatic). On every UI write:
Live fetch from CRM. Rox fetches the current CRM object relevant to the fields being changed.
If the fetch fails, we treat Rox as the latest state, accept the user’s change in Rox, and immediately attempt the CRM write. If the CRM write still fails, we mark the change as a pending CRM write (see below).
If the fetch succeeds, we compare the CRM’s current state against the user’s edit base (the values the user just saw in Rox’s UI). We compute and store a snapshot of the CRM object (or a stable hash of the mapped fields) alongside the user’s intended change.
Conflict handling:
Mismatch (CRM changed since the user last saw it): We abort the write, show the freshly fetched CRM values, and implicitly offer force-overwrite over the freshly fetched latest state.
Match (CRM unchanged): We proceed with a real-time write to the CRM.
Real-time commit semantics:
CRM write succeeds → commit in Rox and record an audit entry.
CRM write fails (validation, 429/5xx, network) → we do not discard the user’s intent. We persist the change in Rox with a “pending CRM write” status and enqueue it for the batch reconciler together with the CRM snapshot captured at edit time.
Why this is safe without clock sync. We never rely on “which timestamp is newer.” We rely on read-check-write: if the CRM object has changed since the user formed their edit, we don’t apply a stale overwrite. If the user explicitly chooses force-overwrite on the freshly fetched state, Rox will attempt to prevail, and if the real-time write still fails, the batch process will take over (see below).
3) Batch Layer
This layer handles two things:
Uni-directional write-backs—especially Clever Columns generated by Rox agents—which must be materialized into CRM fields so customers have them in their warehouse.
Bulk CRM APIs. We use the CRM’s bulk endpoints to stay well below org-level rate limits.
Cell-level sync timestamps. Rox tracks a per-cell “last synced at” time. We select all cells updated since last sync, transform via mappings, and build bulk payloads.
Large payload safety. For heavy
TEXT
columns (KBs per cell), we batch DB reads to bring only what’s required into memory and avoid OOM at scale.Auditable write-backs. Every write-back is recorded in our sharded Postgres within the same transaction—capturing both code exceptions and CRM-returned validation errors.
Cadence & retries. A CRON runs nearly every 15 minutes. Double-writes aren’t harmful in this path — we prioritize eventual consistency and reliability since CRM-side changes aren’t expected to conflict with Rox-sourced enrichments.
Reconciliation of pending real-time writes (from point (3) from the Real-time Layer):
When the user attempted the edit, Rox stored a snapshot of the CRM object (or a hash of the mapped fields) and the intended change.
On the next batch run we fetch the current CRM object and compare it to the stored snapshot:
Different from snapshot → skip the pending write. This is equivalent to “had the real-time write succeeded at the moment of the user’s edit, a later CRM change would have superseded it anyway,” so we do not clobber the newer CRM change.
Identical to snapshot → apply the pending write via the CRM’s bulk/standard API and update audit logs accordingly.
This gives us “exactly-once(ish)” semantics for user edits without clock synchronization: a pending write only lands if the CRM record is still the same record the user edited against.
Operational Guarantees (At a Glance)
Optimistic concurrency for real-time writes (snapshot/compare, not clock-based)
Best-effort atomicity plus no-loss semantics: when CRM rejects a real-time write, the user’s intent is preserved in Rox and reconciled by batch
Deterministic reconciliation of pending writes using snapshot comparison
Idempotent ingestion via explicit Rox ↔ CRM ID mappings stored in both systems
Scalable batch write-backs with per-cell checkpoints and safe replays
CRM-agnostic mappings + validations
Security & Data Stewardship
Least-privilege CRM scopes for read/write.
Audit logs for every write-back (who/what/when/result), retained in Rox’s sharded Postgres.
Warehouse-native posture: customers can move toward Rox writing directly to their warehouse as trust grows.
Admin UX Summary
Configure field mappings (Rox ↔ CRM) and sync direction per field.
Define validation (min/max, length, nullable).
Choose which Clever Columns/read-only fields should be materialized in CRM.
Monitor write-back health via audit logs and per-field sync status, including pending CRM writes and reconciliation outcomes.
Closing
This architecture came from hard-won iteration with customers. If you want to help us evolve this into the definitive system for revenue operations—and build software that delivers real value—join us via our company page.
Last updated