Skip to content

Sync Salesforce to Postgres bidirectionally in under 30 minutes

From OAuth to a live bidirectional flow in under half an hour, with the Salesforce-specific gotchas (governor limits, trigger recursion, SOQL caveats) called out where they bite.

Author
Ruben Burdin · Founder & CEO
Published
June 3, 2026
Read time
15 min

Step 1 — Connect both sides

Salesforce side: connect via OAuth as a dedicated integration user with the minimum profile that grants API access and read/write on the objects you intend to sync. Don't reuse a human user. The integration user should have no UI session, a high API call limit, and a single permission set whose contents are version-controlled.

Postgres side: a role with SELECT, INSERT, UPDATE, and DELETE on the target schema, plus USAGE on the schema itself. Run on SSL. If your Postgres is behind a VPC, prefer reverse SSH tunneling or peering over IP allowlist.

Don't sync as your admin
If the integration user is your Salesforce admin, every webhook firing in the wrong direction reads as 'the admin did it' in your audit log. Future-you will not enjoy that.

Step 2 — Scope the objects

Pick the smallest set of objects that delivers value. Resist the urge to sync "everything". A useful Phase 1 is usually: Account, Contact, Opportunity, one or two custom objects, and the relationship junction tables you actually query in Postgres. Everything else can be added once the pipeline is observable.

  • Filter records at the source using SOQL where-clauses on the connection, not in post-processing.
  • If a record has 200+ custom fields, opt in to only the ones you'll join on or report from.
  • Set up two flows: a one-time backfill via Bulk API 2.0, and the live stream via Platform Events.
See real-time two-way sync in action
Book a demo with real engineers — no sales script.
Book a demo

Step 3 — Map and validate

Field-level mapping is where the integration becomes durable. Map Id to a uniqueness-constrained text column, not a generated bigint. Map booleans explicitly; Salesforce's checkbox never returns null, so a Postgres boolean NOT NULL DEFAULT false is correct.

For validation, run the backfill in shadow mode for an hour: writes happen but trigger no downstream consumers. Compare row counts and a sampled checksum across both sides. Only enable the live event stream once the shadow run is clean.

"We expected the SOQL queries to bite first; in practice, governor limits never even registered. The problem we did hit was a custom trigger writing back into a synced field. That's where the trigger storm came from."
Eng lead, Stacksync customer (anonymized)

FAQ

Frequently asked questions

What's the safest way to handle Salesforce's API governor limits in a bidirectional sync?
Batch reads up to the 200-record limit per call, queue writes per-object with backpressure that pauses on 429, and run the initial backfill with Bulk API 2.0 rather than REST. The combined pattern keeps daily API usage under 2% of a standard org's allotment for tables under 10M rows.
Do we need to write Apex to handle the Postgres side?
No. Stacksync writes to Postgres using standard SQL upserts; the only Salesforce-side configuration is granting OAuth scopes and (optionally) suppressing trigger recursion via a context flag on the sync user. No Apex code is required for the typical setup.

About the author

Ruben Burdin
Founder & CEO

Ruben Burdin is the Founder and CEO of Stacksync, the first real-time and two-way sync for enterprise data at scale. Ruben is a Y Combinator alumni with a strong background in software engineering and business.

All posts by Ruben Burdin

About Stacksync

Stacksync powers real-time, two-way sync between CRMs, ERPs, and databases. Engineers sync data at scale and automate workflows — not dirty API plumbing.

Coworkers laughing in front of a laptop in a casual office setting

Your last integration took months.
Your next one takes a prompt.