Most integration problems I get called into were not caused by bad code. They were caused by the wrong pattern: a synchronous callout where an event should have been, a nightly batch trying to act like real time, a Platform Event doing a job Change Data Capture already does for free. The tools are all fine. The choice is what fails. So instead of listing APIs, let me walk through how I actually decide.

Four questions before any technology

Whenever someone says "we need to connect Salesforce with X", I ask these before opening Setup:

  1. Which system owns the data? The owner is the source of truth, everyone else holds a copy. Half of all sync nightmares come from two systems both believing they own the record.
  2. How fresh does the copy need to be? Be honest here. "Real time" usually means "within a minute", and "within a minute" is a much cheaper problem than actual sub-second delivery.
  3. What volume are we talking about? Ten records an hour and ten million a night are different universes with different tools.
  4. What happens when it fails? Not if. When. If a lost message costs money, you need retries, idempotency, and reconciliation designed in from day one.

The answers usually pick the pattern for you. Here is the map.

Request-reply: synchronous REST

A user or process needs an answer right now: a credit score during a quote, an address validation, a real-time price. This is a synchronous callout from Apex or Flow, and the caller waits.

It is the simplest pattern and the easiest to abuse. Rules I hold to:

  • Set aggressive timeouts. The default 10 seconds of waiting inside a user transaction is an eternity. If the remote API cannot answer in 3 to 5 seconds, the UX needs a different design.
  • Never chain synchronous callouts inside triggers. Callouts do not even run in triggers directly, and the workarounds (future methods fired from triggers calling external APIs per record) are how you meet the 100-callout limit in production.
  • Degrade gracefully. When the remote system is down, the user should be able to keep working, with the enrichment arriving later.

On the inbound side the same pattern is an external system calling Salesforce REST or a custom Apex REST endpoint, and the same advice applies in reverse: keep the transaction small, return fast, push heavy work to a queueable.

Fire-and-forget: Platform Events

Something happened in Salesforce and other systems care, but nobody needs to wait: an order was approved, a case escalated, a contract signed. Publish a Platform Event and move on.

Order_Approved__e evt = new Order_Approved__e(
  Order_Number__c = ord.OrderNumber,
  Total__c = ord.TotalAmount
);
EventBus.publish(evt);

Subscribers (middleware over CometD or Pub/Sub API, or other Apex in the same org) consume at their own pace. The decoupling is the point: the publisher does not know or care who listens.

What bites people in production:

  • Delivery is at-least-once, not exactly-once. Your subscriber must be idempotent. Store the replay ID or a business key and skip duplicates.
  • Events are transient. A subscriber that stays down past the retention window misses messages, so anything financially important needs a reconciliation job on top.
  • Publishing happens on commit for standard publish behavior. If the transaction rolls back, "publish after commit" events do not go out, which is usually what you want, but check the publish behavior setting on the event definition.

Data replication: Change Data Capture

Here is the distinction that saves you custom code: Platform Events tell other systems that something happened in your business process. Change Data Capture tells them that a record changed, automatically, with no publishing code at all.

Enable CDC on Account and every create, update, delete and undelete produces a change event with the changed fields. A downstream data warehouse or search index just subscribes. If your requirement is "keep a copy of these objects in sync elsewhere", CDC is almost always the answer, and I have replaced more than one hand-built trigger-plus-callout sync with it.

Choose Platform Events over CDC when the payload is a business concept ("order approved") rather than a row change, when you want to control exactly what goes in the message, or when the trigger condition is more complex than "the record changed".

Batch: Bulk API 2.0 and friends

Nightly loads, initial migrations, warehouse extracts, anything measured in hundreds of thousands of records: this is Bulk API territory. Bulk API 2.0 handles chunking for you, and modern ETL and iPaaS tools all speak it natively.

The architecture questions that matter more than the API:

  • Upsert with external IDs. Every serious sync needs an external ID field on the Salesforce side so reruns are safe. Insert-only jobs create duplicates the second something retries.
  • Defer what you can. Loading two million records into an object with six active triggers, flows, and a sharing recalculation is a bad night. Plan which automation to bypass during loads (this is where a metadata-driven trigger framework with a bypass switch pays for itself).
  • Order matters. Parents before children, and watch out for record locking when many child records share one parent. Sort your batches by parent ID to avoid lock contention.

Rule of thumb I give every team: user waiting, use request-reply. Systems reacting, use events. Copies syncing, use CDC. Trucks of data, use Bulk. If a design mixes two of these in one flow of data, split it.

The cross-cutting concerns nobody budgets for

Idempotency

Retries are a feature of every reliable pattern, which means duplicates are a certainty. Every consumer needs a way to say "I already processed this". External IDs, replay IDs, or a processed-message log. Build it first, not after the incident.

Error visibility

Integrations fail silently unless you make them loud. Log failures somewhere queryable (a custom object or a logging framework with platform events), alert on volume thresholds, and give admins a retry button. A failed record nobody notices for three weeks is a data quality incident, not a glitch.

Auth done right

Whatever pattern you pick, secrets belong in Named Credentials and server-to-server logins belong on the JWT Bearer flow. I wrote up both in detail: the credential model and the JWT flow.

A worked example

A retailer wants orders from their e-commerce platform in Salesforce, inventory levels back in the store, and a full nightly sync of products. Three requirements, three patterns:

  • Orders flow in through the e-commerce platform's webhooks into middleware, which calls Salesforce REST with an upsert on an external ID. Request-reply inbound, idempotent by design.
  • Inventory changes in Salesforce publish a Platform Event; the middleware subscribes and updates the store. Fire-and-forget, no user waiting.
  • The product catalog syncs nightly over Bulk API 2.0 upserts, automation bypassed during the load window.

Nothing exotic. The value is in not forcing one pattern to do all three jobs.