Skip to content

Integration  ·  Data operations

How to build reverse ETL from the warehouse

The warehouse knows which accounts are expanding and the sales team cannot see it. What follows is the model behind pushing modelled data back into the tools people work in, the prompts that build it, and what changes in production.

Built with Tray Headless

  1. System Snowflake
  2. Step Detect changed rows
  3. Step Field ownership
  4. Step Rate-limited write
  5. System Salesforce
Also Slack

Only changed rows sync, and the CRM field is claimed explicitly so nothing overwrites a value a person typed.

The short answer

What is reverse ETL?

There are four parts to reverse ETL: syncing only the rows that changed instead of the whole model, an explicit claim on which fields the warehouse owns, rate handling that survives an API limit without losing a row, and never overwriting a value a person entered. The common failure is field ownership. Pushing a computed value into a field a rep also edits produces a fight neither side knows they are having, and the rep stops trusting the record.

What matters here

  • Claim the fields the warehouse owns, in writing, before you build. Sharing a field with a person is how trust in the record dies.
  • Sync deltas against a checksum or a modified timestamp. Full syncs burn the API limit and hide the change that mattered.
  • Handle the rate limit as a normal condition, not an error. Every destination will throttle you and losing a row to a 429 is silent.
  • Write to a dedicated field, not a shared one. A computed value belongs somewhere labelled as computed.
  • Report rows synced against rows attempted. Silent partial syncs are the default failure and nothing announces them.

Who this is for

You run data or analytics engineering. The warehouse has the best version of the truth and the people who need it work in tools that have never seen it.

How it works in practice

The sequence, from a model updating to a rep seeing the result.

  1. 1

    The model produces a table shaped for the destination

    One row per record, with the destination id already resolved. Reverse ETL should not be doing joins.

  2. 2

    Changed rows are detected against the last sync

    On a checksum of the synced columns, so an unrelated column changing does not trigger a write.

  3. 3

    Field ownership is checked before writing

    Only fields the warehouse claims. Anything shared with a person is written to a separate computed field instead.

  4. 4

    Writes are batched and rate-limited

    Backing off on a 429 rather than dropping the batch, because a lost row is silent.

  5. 5

    Nothing overwrites a value a person entered

    Human-entered fields are never touched, and the warehouse value goes somewhere labelled as derived.

  6. 6

    Row counts are reconciled after every run

    Attempted, succeeded, failed and skipped. A partial sync that reports success is the common failure.

What reverse ETL is made of

Four parts. The second is a negotiation before it is code.

Delta detection

A checksum over the synced columns, compared against the last successful sync. Full pushes are expensive and hide what changed.

Explicit field ownership

A written claim on which destination fields the warehouse writes. Everything else is out of bounds, permanently.

Rate handling

Backoff, batching and resume. Destination APIs throttle, and a row lost to a rate limit disappears without a trace.

Human precedence

A value somebody typed always wins. Computed values go to their own fields, labelled, so nobody confuses the two.

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. 1

    Set up and claim the fields before writing anything

    This is a negotiation with whoever owns the destination.

    Headless skills build-workflow

    Use build-workflow. The systems in play are Snowflake and Salesforce,
    or whatever we run in those seats.
    
    Before building, produce the field ownership claim: exactly which
    Salesforce fields the warehouse will write, on which objects, and
    confirmation that no human process writes them. Custom fields carrying
    warehouse output should be named as such, so a Health_Score__c is
    visibly not a field a rep is meant to edit.
    
    My rule: never share a field. If a rep edits it, the warehouse writes a
    separate field with a clear derived label instead. A computed value and a
    typed value in the same field is a fight neither side knows they are
    having, and the rep loses faith in the whole record.
    
    Show me any field on my list that a person can currently edit. Those are
    the ones to move.
  2. 2

    Sync deltas, on a checksum

    Full pushes burn the limit and hide the change that mattered.

    Headless skills build-workflow

    Use build-workflow. Build the model into a table shaped for the
    destination: one row per record, the Salesforce id already resolved, and
    only the columns being synced. Reverse ETL should not be doing joins.
    
    Compute a checksum over the synced columns per row. Compare against the
    last successful sync and write only the rows whose checksum changed.
    
    That matters more than it sounds. Without it, an unrelated column
    updating in the model triggers a write to every record, which burns the
    API limit and makes the change you cared about invisible in the noise.
  3. 3

    Write carefully, and treat the rate limit as normal

    A row lost to a 429 disappears without a trace.

    Headless skills tray-patterns

    Batch writes to the destination limit, and treat a rate limit response as
    a normal condition rather than a failure: back off, retry, resume from
    where you stopped.
    
    Never let a batch failure lose rows silently. Record which rows were
    attempted, which succeeded, which failed and why, and make the failed set
    retryable on its own.
    
    Handle these:
    
      A destination record that has been deleted since the last sync
      A destination record that was merged into another
      A picklist value the model produced that the destination will not
      accept, which should fail loudly instead of writing blank
      A field that has been made read-only since the claim was agreed

    The merged-record case is the one that silently writes to a dead id for months. Reconcile against the destination periodically rather than trusting the ids you cached.

  4. 4

    Never overwrite a person

    The rule that decides whether anybody trusts the data.

    Headless skills tray-gotchas

    Use tray-gotchas, then enforce human precedence.
    
    Before writing any field, check whether the current value was last
    modified by a person, not by an integration. If it was, do not
    overwrite it. Write to the derived field and flag the divergence.
    
    Report those divergences. A field the warehouse and a rep disagree about
    repeatedly is either a model that is wrong or a process that should not
    be manual, and both are worth knowing.
    
    Never delete a destination value because the model produced a null. An
    absent value in the model is usually a join that did not match, not a
    statement that the value is empty.
  5. 5

    Reconcile every run

    A partial sync reporting success is the standard failure mode.

    After each run report: rows in the model, rows changed, rows attempted,
    succeeded, failed and skipped for human precedence.
    
    Attempted should equal succeeded plus failed. If it does not, rows went
    missing and the run should fail rather than report success.
    
    Also alert on staleness: if a sync has not completed within its expected
    window, the destination is now showing values that look current and are
    not. Stamp the last successful sync on the record so a reader can tell.
    
    Then report freshness by object and the divergence count. Both are
    invisible unless somebody produces them.
  6. 6

    Check it end to end, then hand the field claim over

    Because ownership is agreed between two teams, not decided by one.

    Run the per-step schema checks and the whole-workflow audit before this
    touches production. Run in dry-run mode for a week first, computing every
    write and performing none, then read the diff with the team that owns the
    destination.
    
    Then open the same workflow in Tray Build so analytics engineering and
    revenue operations can amend the field ownership claim together.

What it connects to

One source, several destinations, and the discipline to write narrowly.

Snowflake

Read the modelled tables and hold the sync state and checksums. The source of truth for everything written.

Reads and writes

Salesforce

Write only the claimed fields, never a value a person entered, and read back for reconciliation.

Reads and writes

Marketo

Push audience and scoring attributes where campaigns run, subject to the same ownership rules.

Writes

Zendesk

Push account context onto the organisation, so support sees what the warehouse knows.

Writes

Slack

Alert on a failed run, a staleness breach or a rising divergence count.

Writes

dbt

Read test results so a failed test blocks the sync instead of pushing bad data into the tools people trust.

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, Jira, 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

This writes into the systems people work in every day. A bad row is seen and believed.

It runs on the platform, not on your laptop

Scheduled syncs with backoff and resume run on the same engine, so a rate limit at 2am is a pause instead of a lost batch.

Every write is attributable

Which run, which model version, which checksum. When a rep disputes a value, that is the answer rather than a shrug.

Credentials are managed, never written into the build

A credential that can write across the CRM is powerful. It lives in your workspace, scoped to the claimed fields where the destination allows it.

Both teams own the claim

The field ownership list opens in Tray Build so analytics engineering and the destination owner amend it together.

A dbt test failure stops the sync

Pushing data that failed its own tests into the tools people trust is worse than pushing nothing.

Questions people ask

Why claim fields explicitly?

Because a field written by both a model and a rep is a fight neither side knows they are having. The rep corrects it, the sync reverts it, and within a month nobody trusts the record.

Why sync on a checksum?

Because an unrelated column changing in the model would otherwise trigger a write to every record. That burns the destination API limit and buries the change that actually mattered.

What should happen when the model produces a null?

Nothing. An absent value is almost always a join that did not match rather than a statement that the field is empty, and deleting a destination value on that basis destroys real data.

How should rate limits be handled?

As a normal condition. Back off, retry and resume from where you stopped, recording which rows were attempted. A row lost to a 429 disappears without any signal at all.

Should a failing dbt test stop the sync?

Yes. Pushing data that failed its own tests into the tools people make decisions in is worse than pushing nothing, because it looks current and gets believed.

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)

Last reviewed September 2026.