Zoho CRM and Postgres: why the source of truth is one database, not the CRM
A CRM is a good interface and a bad source of truth once three systems write to it. Here's why we sync Zoho into one Postgres table with a dedup guard instead of pointing every automation straight at the CRM API.
A CRM is a good interface. It is a bad source of truth the moment more than one system writes to it — a website form, a phone integration, a billing tool, and a spreadsheet someone still edits by hand all pushing records into the same place, each with its own idea of what counts as a duplicate.
A lead created from a website form, updated by a phone integration, and touched again by a billing tool can end up as two or three different records by the time anyone actually looks — not because any one system is broken, but because none of them agree on what makes a record 'the same' as another.
Why we don't wire every automation straight to the CRM API
Point five separate automations at Zoho's API directly and all five inherit Zoho's rate limits, Zoho's outages, and Zoho's own definition of a record — independently, with no shared memory of what any of the others just did. One automation retries a failed write and creates a duplicate; another reads a record mid-update and acts on stale data. None of that is a Zoho problem. It's what happens when nothing sits between the CRM and the systems that talk to it.
The naive fix doesn't fix it
The instinct is to add a queue, a retry policy, or a cache in front of the CRM API and call it solved. That helps with rate limits. It does nothing for the actual problem, which is that five automations still don't agree on what a duplicate looks like unless something outside all five of them defines it once.
One Postgres table, one truth
Instead, we mirror the CRM into a single Postgres table — on Supabase in most of our builds — that every automation reads from and writes to. Zoho stays exactly what it's good at being: the interface the sales team actually looks at, synced in both directions. But the table, not the CRM API, is what every automation treats as the record of what's true. One dedup guard protects every automation that touches it, instead of five separate ones each with their own bugs. That single definition is also what makes the KPI dashboard trustworthy: a number on a dashboard is only as good as the table it's counting rows from.
A CRM's own API isn't built to answer questions like 'how many leads came in this month by source' quickly at scale; a Postgres table built for that purpose is — which is also where the KPI dashboard actually reads from, not from Zoho directly.
The dedup guard, specifically
The guard is an idempotency-key pattern: every write carries a key identifying the same underlying event across retries, so a webhook that fires twice — which is normal behaviour for most third-party providers, not a bug on their end — writes once, not twice. This pattern wasn't designed in a vacuum. It was hardened on FORMAFORCE's stack, more than 85 n8n workflows deep, across 8 separate duplication incidents before it held. Eight incidents isn't a failure count against the pattern — it's the number of times it caught a genuine retry storm before a client ever saw a duplicate invoice or a doubled lead.
What actually plugs into the truth table
The bridge that feeds it pulls from wherever data genuinely originates: the site's own lead forms, a Google Sheet someone is still using day to day, Stripe or SumUp payment events, and the CRM itself. The flow runs both ways — a lead that comes in through the site lands in Zoho automatically, no re-typing, while an update made directly in Zoho flows back into the truth table the same way.
Where this shows up in a build
This is the shape of the CRM and automation option inside our Build tier: phone and payments kept in sync, dedup handled at the database level rather than inside any one automation, and a KPI dashboard reading from the same truth table instead of yet another manual export. The same CRM bridge module also handles the direction automations tend to forget: site and form leads landing in Zoho automatically, invoice stamping, lead redistribution when someone leaves — all reading and writing the same table instead of each inventing its own connection to Zoho. The FORMAFORCE case study walks through what this looks like running in production: /work/paris-sales-ops-automation.
If a CRM is starting to disagree with itself across the systems that write to it, the Build tier on our services page covers exactly this: /services#build.