This article is about those three questions, with the per-CRM specifics that decide the answers. For the wider question of how a CRM should be structured in the first place, see CRM architecture for revenue teams. For where Clay ends and an orchestration tool begins, see Clay vs n8n.
Three sync patterns, three risk profiles
Almost every Clay–CRM setup is one of these:
| Pattern | Flow | Main risk |
|---|---|---|
| Refresh | CRM → Clay → CRM | Overwriting good data with worse data |
| Net-new | Clay list → CRM | Duplicates and ownership conflicts |
| Event-driven | CRM event → Clay → CRM | Loops, races and partial writes |
They fail differently, so they need different safeguards. Refresh needs a blank policy. Net-new needs an identity key. Event-driven needs idempotency and a loop breaker — write-backs that trigger the workflow that caused them are the classic way to burn an API quota overnight.
Decision 1: the identity key
An identity key is the field a write is matched on. Get it right and the same record updates forever. Get it wrong and you manufacture duplicates at machine speed.
HubSpot
HubSpot's own documentation is specific: it deduplicates contacts by looking for a matching value in the Email property, and companies on the primary Company domain name property (HubSpot). Record ID is the unique per-record value used for manual deduplication in imports.
The buried landmine is in the same page: companies created through the API are not deduplicated by the Company domain name property. Clay writes through the API. So a Clay table that creates companies without checking first will happily create a second record for a domain you already have, and no automatic safety net will stop it.
Clay's HubSpot integration gives you the tools to avoid that. Its documented actions include importing objects as a source, Lookup (returning up to 10 results by default), Update (which requires the HubSpot Object ID), Create, Create association between two objects, Retrieve associated objects (20 by default), and finding owners by ID or email (Clay docs).
The safe pattern: Lookup → branch → Update or Create. Never Create on its own.
Salesforce
Salesforce gives you two legitimate keys, and the difference matters:
- Record ID. Clay's Update record action requires it, which means the row must have come from Salesforce in the first place. Perfect for refresh, useless for net-new.
- External ID. Clay's Upsert object action requires an external ID field on the object (Clay docs). This is the right key for net-new, and it only works if someone actually created that field and populated it consistently.
An external ID is only as good as its uniqueness. If two records carry the same value, an upsert has no way to decide which one you meant, and the write fails rather than guessing. That is the correct behaviour and a good reason to enforce uniqueness on the field when you create it.
Two more items from the same docs worth knowing before you design the sync. Clay's Create record action offers an optional duplicate-rule override — do not switch it on to make an error go away; it is a deliberate bypass of your own governance. And the import side has caps: Salesforce list views that are SOQL-compatible are unlimited, non-SOQL views are capped at 2,000 records, and reports (tabular and matrix only) also max out at 2,000. If your segment is larger than that, paginate by a field like created date, or import via SOQL.
Zoho
Clay documents native integrations for HubSpot and Salesforce; Zoho is not among them. The documented route for a tool Clay doesn't natively integrate with is the HTTP API column, which can call any endpoint with GET, POST, PUT or DELETE (Clay docs).
That means you write against Zoho's own API, and Zoho's Upsert Records endpoint is built for exactly this job. Its documentation describes checking for duplicates using duplicate check fields — system-defined unique fields per module, plus user-defined fields marked "do not allow duplicate values" — and you can control the order of that check with a duplicate_check_fields array in the request. A match updates the existing record; no match inserts a new one; and a maximum of 100 records can be inserted or updated per API call (Zoho).
Two practical consequences. First, batch to 100 and design your table to chunk, not to fire one call per row. Second, the duplicate check order is yours to set, so set it deliberately rather than inheriting the default.
One security note that applies to every HTTP API write: Clay's docs warn that credentials typed directly into the Headers field are visible in plain text to anyone with access to that table column, and recommend saving them as a workspace-level HTTP API (Headers) account, which Clay encrypts and makes reusable. Use the account. A CRM write token sitting in a shared table is a real incident waiting for a new teammate.
Decision 2: field ownership
Every field in your CRM should have exactly one owner. Write it down — this table is the artifact, and it prevents more arguments than any amount of process documentation.
| Owner | Examples | Rule |
|---|---|---|
| Human | Deal stage, next step, notes, opt-out status | Clay never writes. Ever. |
| CRM / system | Record ID, created date, owner assignment | Read-only to Clay |
| Clay | Enriched firmographics, fit score, score reason, research findings, signal dates | Clay is the source of truth and may overwrite its own values |
| Shared, with rules | Phone, email | Clay may fill when empty, never replace a human-verified value |
The "shared" row is where most damage happens. A rep confirmed a direct line on a call; two weeks later an enrichment run replaces it with a switchboard number. Nothing errored, and the CRM is now worse. The fix is structural, not procedural: give Clay its own field. phone_enriched next to phone, with a rule about which one the UI shows. Separate fields make disagreement visible instead of destructive.
Decision 3: the blank policy
The rule is one line: a blank result never overwrites a populated field.
The tooling supports this. Clay's Salesforce Update record action documents optional blank value handling, and its HubSpot actions include options to exclude empty properties on import and remove blank values from Lookup and Retrieve results (Clay docs, Clay docs). Those settings exist because the failure they prevent is common and expensive.
The subtler version: a worse result should not overwrite a better one either. An enrichment that returns a generic info@ address should not replace a verified personal one. That is why the next decision exists.
The MitHub Write-Back Contract
Fill this in before the first live write. It takes fifteen minutes and it is the document you hand to whoever inherits the system.
| Clause | Question | Example |
|---|---|---|
| Key | What is the write matched on? | HubSpot Object ID from a prior Lookup |
| Direction | Who wins on conflict? | CRM wins on human-owned fields; Clay wins on Clay-owned fields |
| Ownership | Which fields may Clay write? | The eight listed in the field-ownership table, and no others |
| Blank policy | What happens on an empty result? | Skip the field, log the miss, do not clear |
| Provenance | How will we trace a bad value? | Source, confidence, enriched-at date, run ID on every Clay-owned field |
| Conflict rule | What if Clay and a human disagree? | Write to the parallel Clay field; flag for review above a threshold |
| Volume & cadence | How much, how often, batched how? | Nightly, 100 records per call, capped at N per run |
Provenance is the clause people skip and later wish they hadn't. Four small fields — source, confidence, enriched_at, run_id — turn "this data is wrong" from an argument into a query. You can find every record a bad provider touched, re-run exactly those, and prove what changed. Without them, a single bad enrichment run is unwindable.
The reverse direction: letting the CRM trigger Clay
For HubSpot and Salesforce, Clay's native source actions pull records in on a schedule. For Zoho, or for anything that needs to be event-driven rather than scheduled, the CRM pushes instead.
Zoho's webhooks are configured under Setup → Automation → Actions → Webhooks and then associated with a workflow rule, supporting POST, GET, PUT and DELETE. The documented limits shape what you can build: up to 6 webhooks per workflow rule (1 instant plus 5 time-based), a maximum of 10 CRM fields transferable per webhook, only ports 80 and 443, and daily call ceilings that vary by edition (Zoho).
Ten fields per webhook is the constraint that bites. Don't try to send the record — send the ID and the trigger reason, then have the receiving system fetch what it needs. That is better design anyway: the payload stays small, the data is fresh at read time, and the webhook doesn't break every time someone adds a field.
When the event-driven path gets complicated — retries, branching, approvals, several systems in sequence — that logic does not belong in a Clay table. Clay is excellent at enriching and scoring a row; it is not an orchestrator with error handling. Hand it to a workflow tool, as CRM automation with n8n describes.
Test before you trust
- Sandbox or a throwaway segment first. Ten records, chosen to include one that already exists, one that definitely doesn't, and one with a messy near-duplicate.
- Run the write once and inspect all ten by hand. Count the records before and after. If the count grew by more than the number of genuinely new records, your key is wrong.
- Run it a second time with no changes. Nothing should happen. If the second run creates or modifies anything, the write is not idempotent and a retry will corrupt data.
- Test the blank path deliberately. Force an enrichment to fail and confirm the existing value survived.
- Check the unwind. Can you identify and revert everything this run touched using
run_id? If not, add provenance before scaling. - Then scale, with a volume cap on the first full run.
That sequence is unglamorous and it is exactly what separates an integration that quietly works for two years from one that generates a data-quality project. The score you worked so hard to calibrate in lead scoring in Clay only matters if it lands on the right record, in a field someone trusts, with a timestamp that proves it is current — and then gets acted on, which is where lead routing takes over.
