Integration · Data operations
How to build a CRM to warehouse sync
The board asks how pipeline looked in March and the CRM only knows how it looks today. Here is the model behind a sync that keeps the history, the prompts that build it, and what it takes to run in production.
Built with Tray Headless
- System Salesforce
- Step Delta extract
- Step Land raw
- Step History tables
- System Snowflake
Raw lands before anything is modelled, and row counts are reconciled every run so a partial load cannot publish.
The short answer
What is a CRM to warehouse sync?
There are four parts to a CRM to warehouse sync: capturing history rather than overwriting current state, handling deletes and merges so a disappeared record does not silently distort a trend, landing raw before modelling so a transformation bug is recoverable, and reconciling row counts so silent data loss is impossible. The part that goes wrong most often is history. A sync that mirrors current state answers what is true now, and every interesting question about pipeline is about what changed.
What matters here
- Capture change over time, not current state. Every useful pipeline question is about what moved, and a mirror cannot answer one.
- Handle deletes explicitly. A record that vanishes from the source and from the warehouse rewrites history that already happened.
- Land raw first, model second. A transformation bug found in week three is recoverable if the raw extract is still there.
- Reconcile row counts every run. Silent partial loads are the most common warehouse failure and nothing announces them.
- Watch for schema drift. A field added or a picklist value renamed in the CRM breaks a downstream model quietly.
Who this is for
You run data or analytics engineering. Reporting is built on a nightly export that nobody fully trusts, historical questions cannot be answered, and a failed load is discovered when a dashboard looks wrong.
How it works in practice
Everything that sits between a record changing in the CRM and a dashboard being able to answer for it.
- 1
Changed records are extracted against a watermark
Not a full table copy each night, because the volume grows and the window does not.
- 2
Raw lands first, untransformed
A dated raw layer, so a modelling bug found later is fixed by reprocessing, not by asking for the data again.
- 3
History is captured as change over time
Each version of a record with its valid-from and valid-to, so March can be reconstructed exactly.
- 4
Deletes and merges are recorded, not applied
A record disappearing is an event with a date, not a reason to erase what was true before it.
- 5
Row counts are reconciled every run
Source against raw against modelled. A mismatch fails the run rather than publishing a quiet undercount.
- 6
Schema drift is detected before it breaks a model
A new field or a renamed picklist is reported the day it appears.
What the sync is made of
Four parts, and the first is the difference between a mirror and an analytics asset.
History capture
Slowly changing dimensions on the fields that matter: stage, amount, close date, owner. Current state answers almost nothing worth asking.
Delete and merge handling
A soft delete with a date, never a physical one. A merged or deleted record must not remove history that was true at the time.
Raw before modelled
A dated raw layer that survives, so a transformation bug is a reprocess instead of a re-extract.
Reconciled counts
Source, raw and modelled compared every run, with a mismatch failing loudly. Silent partial loads are the default failure mode.
The Tray Headless prompts
Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.
Once per project, run
/tray-workflows:set-workspace
to pick the workspace these build in. Point it at a sandbox first.
- 1
Set up and establish what changes and how you can tell
Everything depends on being able to detect change reliably.
Headless skills
build-workflowtray-patternsUse build-workflow. The systems in play are Salesforce and Snowflake, or whatever we run in those seats. For each object I care about, Opportunity, Account, Lead, Contact and User, I need: the modified timestamp field, whether deletes are soft or physical, whether the API exposes a change data feed, and how merges are represented. That last one matters most. A merge looks like a delete plus an update, and if the sync treats it as a delete the history disappears.
- 2
Extract deltas, land raw, never transform on the way in
So a modelling bug is a reprocess, not an apology.
Headless skills
build-workflowUse build-workflow. Extract records modified since the last watermark, with a few minutes of overlap so nothing straddling the boundary is missed. Land the raw payload exactly as received into a dated raw table. Do not transform, rename, cast or filter on the way in. The raw layer is your insurance: a transformation bug found in week three is a reprocess if the raw is intact, and an apology if it is not. Advance the watermark only after a fully successful load. A partial load that advances the watermark silently drops every record in the gap, and nobody finds out until a number looks wrong months later.
- 3
Capture history, not current state
Because every interesting question is about what changed.
Model the raw into history tables, not a mirror. For opportunities, track a new version whenever stage, amount, close date, owner or forecast category changes. Each version carries valid_from and valid_to, with the current version open-ended. That makes these answerable, and none of them are answerable from a mirror: What did pipeline look like on the last day of March How many deals slipped out of Q2 and where did they land What was the amount when this deal entered negotiation Which deals changed owner mid-cycle and what happened to them Keep a current-state view on top for convenience, but build it from the history instead of maintaining it separately.
This is the whole reason to build a warehouse sync rather than pointing a dashboard at the CRM. A mirror only ever answers what is true now.
- 4
Handle deletes, merges and schema drift
The three things that corrupt a warehouse quietly.
Headless skills
tray-gotchasUse tray-gotchas, then handle these explicitly: Deletes. Soft delete with a deleted_at timestamp. Never remove the row. A deleted opportunity was real when it existed and history should still show it. Merges. Record the merge as an event linking loser to survivor, and keep both histories. Treating a merge as a delete erases activity that happened. Schema drift. Compare the source schema each run. Report new fields, removed fields and renamed picklist values the day they appear. A renamed stage silently breaks every model that groups by it, and the dashboard just shows a new category nobody notices. Hard deletes at source. If the API supports a deleted-records feed, read it. Otherwise reconcile ids periodically, because a physically deleted record simply stops appearing and no delta will ever mention it.
- 5
Reconcile counts, every run
Silent partial loads are the most common failure and the least visible.
After every run, reconcile three numbers per object: the source count of records modified in the window, the rows landed in raw, and the rows modelled. A mismatch fails the run and alerts. Do not publish a partial load and do not let the next run paper over it. Also check for staleness: if an object has had no changes for longer than its normal quiet period, that is a broken extract rather than a quiet day. Alert on it. Then report per run: rows by object, run duration, reconciliation results, schema drift detected and watermark position. Somebody should be able to answer whether last night worked in ten seconds.
- 6
Validate, then hand it to analytics engineering
So the object list and the tracked fields belong to the team modelling them.
Run the per-step schema checks and the whole-workflow audit before this touches production. Then open the same workflow in Tray Build so analytics engineering can add an object, change which fields are tracked historically, or adjust the schedule in the visual canvas. The reporting requirement changes constantly, and the extract should keep up without a sprint.
What it connects to
One source, one destination, and the people who need to know when a load failed.
Salesforce
Read changed records against a watermark, plus the deleted records feed where the API exposes one.
Reads
Snowflake
Land raw, build history tables, and hold the reconciliation results alongside the data they describe.
Writes
dbt
Trigger modelling after a successful load and read the test results, so a failed test blocks publication.
Reads and writes
Slack
Alert on a reconciliation mismatch, a stale object or schema drift, to the team that owns the models.
Writes
Looker
Read which dashboards depend on which tables, so a failed load can say what is affected instead of that something broke.
Reads
Same build, other stacks
The design does not change if you run something else in one of these seats. The same prompts build it against Microsoft Dynamics 365, Google BigQuery, Microsoft Teams, Power BI, HubSpot or Databricks.
Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.
Running it in production
Everything downstream believes this data. A quiet failure is worse than a loud one.
It runs on the platform, not on somebody's machine
Scheduled extracts, retries and reconciliation run on the same engine, with a watermark that survives a failure instead of advancing through it.
Every run is accounted for
Rows by object, reconciliation result and watermark position, recorded per run. Did last night work should take ten seconds to answer.
Credentials are managed, never written into the build
A read credential over the entire CRM is one of the most sensitive things you hold. It lives in your workspace, scoped to read and rotatable.
Analytics engineering owns the object list
Objects, historically tracked fields and schedules open in Tray Build, changed by the team that models the output.
A mismatch fails the run
It does not warn and continue. A partial load that publishes is worse than no load, because the dashboard looks fine and the number is wrong.
Questions people ask
Why capture history, not mirror current state?
Because every interesting question is about what changed. What pipeline looked like in March, how many deals slipped, what the amount was at a given stage. A mirror cannot answer any of them.
Why land raw before modelling?
Because a transformation bug found in week three is a reprocess if the raw extract survives, and a re-extract request if it does not. The raw layer costs storage and saves weeks.
How should deletes be handled?
Soft, with a timestamp, never by removing the row. A deleted opportunity was real when it existed, and erasing it rewrites history that already happened.
Why reconcile row counts every run?
Because silent partial loads are the most common warehouse failure and nothing announces them. The dashboard looks fine and the number is quietly wrong for months.
What is schema drift and why does it matter?
A field added, removed or a picklist value renamed at source. A renamed stage breaks every model grouping by it, and the dashboard just shows a new category nobody questions.
Vibe-coding app guides
Vibe-code an entire app with Helix
This moves the data between systems. It does not give anybody a screen to work in. Build that app in Claude Code, Codex or Cursor, then deploy and run it governed on Tray Helix. Same kind of guide, same kind of prompts.
How to build an executive KPI dashboard (opens helix.tray.ai in a new tab)
Related guides
Revenue operations
How to build pipeline hygiene automation
Detect the staleness that matters, nudge the owner rather than the report, escalate on the deals that count, and measure whether it worked. The prompts that build it.
Revenue operations
How to build lead deduplication and merge
Detect on a scored rule set, survive the merge with field-level rules, keep the activity history, and never silently reassign an owner. The prompts that build it.
Data operations
How to build data quality monitoring
Test what breaks decisions, alert the owner rather than a channel, and say what depends on a failure. The Headless prompts that build it.
Data operations
How to build schema change management
Detect a source change before it breaks a model, resolve what depends on it, and tell the owner in time to act. The Headless prompts that build it.
Platform engineering
How to build a change data capture pipeline
Read the log instead of polling a timestamp, overlap the snapshot with the stream, and keep deletes and order intact. The Headless prompts that build it.
Last reviewed September 2026.