Skip to main content
Architecture

Choosing the right Salesforce integration pattern

Request-reply, fire-and-forget, batch sync, remote call-in or event-driven — a practical guide to picking the pattern before you write the callout.

Vikas Poddar · July 3, 2026 · 11 min read

Most integration problems in Salesforce are not coding problems. They are pattern-selection problems that surfaced eighteen months later, under production volume, on the day a downstream service had an outage.

The decision is usually made implicitly — someone needs data in another system, writes a callout in a trigger, and the pattern is chosen by accident. Here is how to choose it deliberately.

Start with four questions

  1. 01Does the user need the result before their save completes? If not, nothing should be synchronous.
  2. 02What happens if the downstream system is unavailable for an hour? If the answer is 'we lose data', you need a queue.
  3. 03What volume will this carry at peak, not at demo?
  4. 04Who owns the failure when it happens, and how do they find out?

The fourth question eliminates more bad designs than the other three combined. An integration with no owner and no alerting is an outage you will discover from a customer.

Request and reply

Salesforce calls out and waits for a response, which is used immediately. Appropriate when the user genuinely cannot proceed without the answer — a credit check during application, an address validation, a real-time price lookup.

  • Never place this in a trigger. Callouts in the save path couple your user experience to someone else's uptime.
  • Use a screen flow or an LWC calling imperative Apex, so the wait is visible and cancellable.
  • Set an explicit timeout and design the UI for the timeout case, because it will happen.

Fire and forget

Salesforce sends data and does not wait. This is the most common pattern and the most commonly broken one, because 'do not wait' is frequently implemented as 'do not check'.

Done properly, fire-and-forget means publishing a platform event and letting a subscriber handle delivery, retry and failure logging. Done improperly, it means an @future method with an empty catch block.

// Publish from a single trigger handler — the callout never blocks the save.
List<Order_Sync__e> events = new List<Order_Sync__e>();
for (Order o : changedOrders) {
    events.add(new Order_Sync__e(
        Order_Id__c       = o.Id,
        Idempotency_Key__c = o.Id + '-' + String.valueOf(o.SystemModstamp.getTime())
    ));
}
List<Database.SaveResult> results = EventBus.publish(events);
IntegrationLogger.logPublishFailures(results);

The idempotency key matters. Retries are inevitable, and without a key the downstream system cannot tell a retry from a genuine second event.

Batch data synchronisation

Scheduled, bulk movement of records in either direction. Right for high-volume, latency-tolerant data — nightly product catalogue refreshes, financial postings, historical loads.

  • Use Bulk API 2.0 for inbound volume rather than row-by-row REST calls.
  • Track a high-water mark so each run only processes what changed since the last successful run.
  • Make the job resumable. A batch that must be rerun from the beginning after a mid-run failure will eventually exceed its window.
  • Emit a reconciliation summary on every run — counts in, counts out, counts failed.

Remote call-in

An external system calls Salesforce. Use standard REST or SOAP APIs where they fit, and custom Apex REST services where you need to encapsulate business logic or shield callers from your schema.

  • Authenticate with a Connected App and OAuth 2.0 client credentials or JWT bearer flow — never a shared username and password.
  • Give the integration user a dedicated profile with least-privilege access.
  • Bulkify your custom endpoints. Callers will batch requests whether or not you designed for it.
  • Version the endpoint from day one. Changing an unversioned endpoint breaks every caller simultaneously.

Event-driven

Platform events and Change Data Capture decouple producers from consumers entirely. This is usually the right answer when more than two systems care about the same change, or when you want the publisher to be unaware of who is listening.

The trade-off is delivery semantics. Platform events are at-least-once, not exactly-once, and the event bus has a retention window. Consumers must be idempotent and must handle replay after downtime. Design for both before you go live, not after your first outage.

The non-negotiables, whichever pattern you pick

  • An integration log object with the payload reference, attempt count, status and error — queryable by someone who is not a developer.
  • A dead-letter destination for messages that exhaust their retry budget, so nothing disappears.
  • Alerting routed to a person, not to a debug log nobody reads.
  • A reconciliation job that periodically proves both systems still agree.
  • Documented API limit consumption, modelled at peak volume rather than average.

Pick the pattern deliberately, write down why, and build the observability at the same time as the happy path. The integrations that survive are the ones that fail loudly.

Start here

Tell us what is not working. We will tell you what it takes to fix it.

Send us the problem — a stalled implementation, an integration that keeps failing, an AI pilot that will not reach production. You get a considered response from a certified engineer, not a sales sequence.

24/7 support — response within one business hour