All segments

n8n Salesforce: Batch Syncs Without Hitting API Limits

n8n Salesforce syncs need batching and external-ID upserts to survive Salesforce's rolling API allocation without creating duplicate records.

  • Published
  • Reading time 15 min read
  • Author Nafiul Hasan
n8n Salesforce: Batch Syncs Without Hitting API Limits. Diagram: attempts, spaced. RUN n8n Salesforce: Batch SyncsWithout Hitting API Limits WIDENING INTERVALS pointerflow.com

Short answer

A self-hosted n8n Salesforce sync survives high order volume when you batch calls to stay under Salesforce's rolling API allocation and match every upsert on an external ID field, so a retried run updates the existing record instead of creating a second one, and Salesforce's Bulk API handles the volume more cheaply than one call per record.

Why an n8n Salesforce sync breaks at volume, not at setup

An n8n Salesforce workflow usually works the first time you build it. You trigger it on a Shopify order, map five fields, call the Salesforce node, watch a Contact and an Opportunity appear. It’s the next order, the one that arrives while yesterday’s batch is still catching up, that finds the gaps. n8n Salesforce syncs built for a demo rarely survive contact with a $3M–$30M store’s actual order volume, because the two failure modes that matter, Salesforce’s own API allocation and duplicate records from retried runs, don’t show up until you’re pushing real traffic through the workflow for weeks, not minutes.

This isn’t a piece about connecting n8n to Salesforce for the first time; the native node handles authentication and field mapping well enough that most teams get that far unassisted. It’s about the two things that turn a working proof of concept into a sync that silently loses data or quietly doubles it: staying inside Salesforce’s request budget, and making every write safe to repeat. If you’re running Shopify Plus or a comparable subscription platform and sending Salesforce more than a trickle of records a day, both of these will find you.

What you need before you build this sync

You need write access to the target Salesforce objects and, critically, the ability to add a custom field, since the external ID pattern this article builds toward depends on one. If you don’t have System Administrator or an equivalent profile in the Salesforce org, get someone who does involved before you start, because adding an external ID field after a sync is already live means backfilling it against every record you’ve already written.

You need a self-hosted n8n instance, not because the cloud version can’t call Salesforce, but because the batching and throttling patterns this article builds assume you control execution timeouts, retry behaviour and, if you’re running queue mode, worker concurrency. A managed instance with fixed limits fights you at exactly the settings this article tunes.

You need a Salesforce sandbox to test against. Building and breaking a batching strategy against production data is how a rate-limit mistake becomes a client-visible outage instead of a Tuesday-afternoon fix. Sandboxes carry their own separate API allocation from production, so load-testing there costs nothing against the budget your sync will actually run on.

Finally, you need to know roughly how many records per hour this sync will move at peak, not on average. A launch-day sale or a Black Friday order spike is when a sync that behaved fine in testing meets Salesforce’s short-window throttling for the first time, and that’s not a number you can estimate from a quiet Tuesday’s traffic.

Decide which object owns each field

Before any batching or retry logic, settle where each piece of data lives permanently. A sync that lets both Shopify and Salesforce edit the same field invites conflicts that no amount of batching fixes. The common pattern for ecommerce: Shopify owns product, inventory and order data; Salesforce owns the sales and service relationship, meaning Lead status, Opportunity stage and case history. Data flows from Shopify into Salesforce on new orders and customer updates, and rarely flows back except for account status changes that Shopify needs to reflect, such as a flagged fraud hold.

Map this explicitly before you write a single node. For each field you’re syncing, write down which system is the source of truth and what happens if the other system’s value differs. This sounds like process overhead until the day a sales rep manually corrects a customer’s shipping address in Salesforce and your sync overwrites it on the next Shopify order, because nobody decided in advance that Salesforce should win that field.

Decide sync direction per object, not per workflow. It’s common to need Shopify-to-Salesforce for Contacts and Opportunities but Salesforce-to-Shopify for a single flag field like a VIP tier. Building these as separate, smaller workflows rather than one bidirectional monster makes each one easier to reason about when it fails, and it isolates a batching problem on the high-volume order sync from the low-volume flag sync that never needs it.

Add an external ID field for idempotent upserts

The external ID field is the setting most teams skip, and it’s the one that causes the most support tickets later. In Salesforce, create a custom field on the target object, an Account, Contact or Opportunity, or whatever custom object you’re writing to, and in its field definition mark it as both External Id and Unique. A common name for this field is something like Shopify_Order_ID__c, holding the Shopify order number, or Shopify_Customer_ID__c on the Contact object, holding the customer’s Shopify ID.

With that field in place, your n8n workflow calls Salesforce’s upsert operation instead of separate create and update calls, passing the external ID value alongside the record data. Salesforce does the matching: if a record with that external ID value already exists, it updates it; if not, it creates one. This single change is what makes the sync safe to retry. Without it, every create call is a fresh insert, and a workflow that fails halfway through a batch and gets re-run duplicates every record it had already written before the failure.

The native n8n Salesforce node supports upsert as an operation choice, with a field to specify which external ID field to match on. If you’re calling Salesforce’s REST API directly through an HTTP Request node instead, the upsert pattern is a PATCH request to a URL built from the object name and external ID field, with the ID value as the final path segment, which Salesforce treats as create-or-update depending on whether that value already exists on a record.

The mistake to watch for: teams add the external ID field, then keep using create calls out of habit, or because the workflow was built before the field existed and nobody revisited it once volume grew. Check every write operation in the workflow against this question: if this exact record is sent twice, does Salesforce end up with one row or two? If the answer is two, it’s not using upsert on the external ID yet.

Batch calls to stay inside Salesforce’s API allocation

Salesforce enforces a rolling API request allocation per org, sized by edition and number of licensed users, and every REST call, Bulk API batch or SOAP call counts against it. The exact number for your org isn’t something to memorise from a blog post; it changes with edition, add-on licences and Salesforce’s own packaging over time, and you can see your org’s current allocation and recent usage under Setup, in the System Overview page. Treat any specific figure you find elsewhere as expired and check your own org.

What matters operationally is that one API call can carry one record or many, and the method you choose changes that ratio by orders of magnitude.

MethodBest forTrade-off
Single-record REST call to an object’s endpointA handful of records per runSimplest to build, but burns one API call per record, so it scales linearly against your allocation
Composite or Composite Graph requestDozens of records, especially when related objects need updating togetherBundles several operations into one call, but the number of sub-requests it can carry in a single call is still capped
Bulk API 2.0Hundreds or thousands of records per runOne job absorbs a large number of records against a small number of API calls, but it runs asynchronously, so your workflow submits the job and then polls for its result instead of getting a response straight back

For most Salesforce n8n syncs pushing Shopify order or catalogue data at a $3M–$30M store’s volume, Bulk API 2.0 is the only method built for the ratio of records to API calls that keeps you comfortably inside your allocation. Salesforce chunks Bulk API jobs into batches automatically rather than asking you to set a per-batch record count, so check Salesforce’s current Bulk API documentation for how it handles very large jobs rather than assume a figure from an older integration.

If you’re staying with the simpler REST-per-record approach, because the sync volume genuinely doesn’t justify Bulk API’s extra complexity, use n8n’s Split In Batches node to chunk the incoming record set rather than looping over every item in one pass. A reasonable starting batch size is 200 records per loop iteration: small enough that one slow or failing batch doesn’t stall the whole execution for minutes, large enough that you’re not spending the node’s per-batch overhead on tiny groups. Adjust from there once you can see how your own workflow behaves under real volume, since the right number depends on average record size and how many fields each write touches.

Throttle concurrency so bursts don’t trip limits

Salesforce’s rolling daily allocation isn’t the only limit that matters. It also enforces short-window concurrency and burst limits, independent of your daily total, and exceeding those returns an error like REQUEST_LIMIT_EXCEEDED even when you’re nowhere near your daily budget. This is the limit that catches teams who’ve correctly batched their calls but still fire every batch back to back with no pacing between them.

Add a Wait node between batch iterations in your n8n workflow, set to a short fixed delay, a starting point of one second is reasonable for most order-volume syncs, longer if you’re also hitting Salesforce with other integrations from the same org concurrently. This spaces requests out in time rather than changing how many records each request carries, which is the difference between staying under the daily allocation and staying under the short-window throttle: batching addresses the first, pacing addresses the second, and you need both.

If you’re running n8n in queue mode with multiple worker processes, be aware that concurrency at the worker level compounds this problem in a way a single-instance setup doesn’t. Two workers each processing a Salesforce-bound workflow at the same time double your effective request rate against Salesforce even if each individual workflow paces itself correctly. Either dedicate a lower-concurrency worker pool to Salesforce-bound workflows or add a shared rate-limiting mechanism, such as a queue or semaphore pattern outside n8n itself, if multiple workflows write to the same Salesforce org concurrently.

Build a retry path that can’t duplicate a record

n8n retries a failed execution, whether triggered manually, through an Error Workflow, or via a node’s own retry-on-fail setting, by running the workflow again from the point you’ve configured, not by resuming a partially completed Salesforce write. If the failure happened after some records in a batch had already been upserted, a naive retry sends the whole batch again. With external ID upserts in place, this is safe: Salesforce matches the already-written records and updates them harmlessly instead of duplicating them. Without external ID matching, this is exactly how a retry doubles a batch, which is why external ID upserts matter as much for retries as for the sync’s steady-state behaviour.

Configure retry-on-fail at the node level for the Salesforce call itself, with a small number of attempts and a delay between them, so a transient network error or a brief Salesforce-side throttling response resolves without human intervention. Separately, configure an Error Workflow at the workflow level to catch failures that exhaust the node’s own retries, so a genuine problem, an authentication token that’s expired or a field validation rule that’s started rejecting your data, surfaces as an alert rather than a silently stalled sync.

Log which batch failed and why before retrying it, not after. A workflow that catches an error, retries immediately and only logs on the second failure loses the detail of what the first failure actually was, which matters when the cause is something like a validation rule change on the Salesforce side that will keep failing every retry until a human fixes the underlying rule, not the sync.

Log every batch outcome somewhere you can query

Treat the sync’s own history as data worth keeping, not just the records it moves. For each batch, record the batch’s size, its outcome, success, partial failure or full failure, and the Salesforce-assigned IDs or error details it returned, in a datastore you control, whether that’s a dedicated table, a spreadsheet for smaller volumes, or a logging service you already use elsewhere. This is what makes a partial failure diagnosable: if a batch of 200 records has 3 failures, you need to know which 3 and why, not just that the batch as a whole didn’t fully succeed.

This log is also how you answer the question a stakeholder eventually asks: did that order actually make it into Salesforce? Without a queryable record of every batch’s outcome, answering means searching Salesforce manually for a specific record and hoping it’s there, which doesn’t tell you whether it arrived on time or after a delayed retry.

The step most teams get wrong

Most n8n Salesforce syncs get the field mapping right, get authentication right, and even get batching roughly right once volume forces the issue. The step that gets missed is the external ID field itself, specifically, adding it after the sync has already been running on plain create calls for weeks or months. By the time someone notices duplicate Contacts or Opportunities piling up in Salesforce, the sync has already written some unknown number of duplicates, and retrofitting external ID matching doesn’t retroactively merge records that already exist as separate rows.

The fix at that point is a one-time deduplication pass in Salesforce, using its own duplicate management rules or a manual merge, before switching the sync over to upsert calls. This is a real project, not a five-minute setting change, and it’s entirely avoidable by building the external ID field and upsert pattern in from the first workflow version, even when volume is low enough that duplicates aren’t yet a visible problem. Low volume is exactly when this is cheapest to fix, because there’s less to clean up.

How do Salesforce API limits actually work?

Salesforce meters API usage against a rolling 24-hour allocation tied to your org’s edition and licensed user count, visible under Setup, System Overview. Every REST call, Bulk API batch and SOAP call counts against this same budget regardless of which integration made it, so if your n8n sync shares an org with other connected tools, they’re all drawing from one pool.

Separately from the daily allocation, Salesforce enforces short-window concurrency limits that cap how many requests can be in flight or arrive within a short burst, independent of how much of the daily allocation you’ve used. A sync can trip this limit on a quiet day if it fires requests too quickly, and stay well under it on a busy day if it’s properly paced. This is why pacing, not just batching, matters: batching reduces how many calls a given volume of records needs, pacing controls how fast those calls arrive.

What happens when you go over the limit?

Exceeding the daily API allocation blocks further API access for the org until the rolling window clears, which for a business relying on the sync for same-day order visibility in Salesforce is a real operational problem, not just a workflow error. Exceeding the short-window concurrency limit instead returns an immediate error on the offending calls, without blocking the whole org, which is more survivable but still means whatever records were in that batch didn’t make it through and need a retry.

Both cases are recoverable if your workflow logs failures and has a retry path, which is why logging every batch outcome and building a retry path around external ID upserts matters, rather than treating a passing test run as proof the sync is finished. A sync that’s never been tested against its own failure modes finds them for the first time in production, usually during the highest-volume period you have.

How do you verify the sync actually worked?

Verification means more than checking that the workflow’s last execution shows green in n8n. Cross-check counts: for a given time window, does the number of orders Shopify recorded match the number of upserted records your batch log shows for that same window? A gap here, even a small one, is worth investigating immediately rather than waiting for a customer or a sales rep to notice a missing record.

Spot-check individual records for field accuracy, not just presence. A record existing in Salesforce doesn’t confirm every mapped field landed correctly; a validation rule on the Salesforce side can silently reject one field while still allowing the rest of the upsert through, depending on how the rule and the API call are configured. Pull a handful of recently synced records each week and compare their field values against the Shopify source.

Finally, verify the retry path itself, deliberately, in your sandbox. Force a failure partway through a batch, let the Error Workflow or retry-on-fail setting run, and confirm the record count in Salesforce afterwards matches what it should, not double. This is the one test that actually proves the external ID upsert pattern is doing its job, and it’s cheap to run in a sandbox before you ever need it to work correctly in production.

Getting a Salesforce sync this reliable is an AI agents and automation problem before it’s a Salesforce problem: the workflow logic that decides when to retry, how to batch, and what counts as a failure worth paging someone over is exactly the kind of operational automation layer Pointerflow’s AI agents work covers for stores running self-hosted integrations at real volume.

Sources

  • No external figures are quoted in this article. It’s written from Salesforce’s own documented API architecture (REST, Bulk API 2.0, upsert-by-external-ID, and the System Overview usage page) and n8n’s documented workflow features (Split In Batches, Wait, Error Workflow and retry-on-fail), described at the level of mechanism rather than any specific numeric limit, since those figures vary by Salesforce edition and change over time.

Frequently asked

What is n8n Salesforce integration used for?

n8n Salesforce integration usually moves order, customer or product data from Shopify or another store platform into Salesforce as Leads, Contacts, Accounts or Opportunities, so sales and service teams work from one record instead of switching systems. Some builds run the sync the other way, pushing account status or case history back into store metafields.

Can n8n replace Salesforce's own API rate limiting?

No. n8n controls how fast your workflow sends requests, but Salesforce still enforces its own allocation and short-window throttling independently. Slowing your workflow down avoids tripping those limits; it doesn't raise them. If you need a bigger allocation, that's a licensing conversation with Salesforce, not a workflow setting.

Does n8n have a built-in Salesforce node?

Yes, n8n ships a native Salesforce node covering common operations against standard and custom objects, including upsert by external ID. For very high volume or operations the native node doesn't expose, teams often drop to an HTTP Request node calling Salesforce's REST or Bulk API directly, which needs more setup but gives full control over batching.

Does every Salesforce object need its own external ID field?

Yes, if you're syncing to more than one object, each needs its own external ID field, since Salesforce matches per object, not globally. A Contact and an Opportunity created from the same order need separate external ID fields, each marked External Id and Unique, even if they share the same underlying source value.

How do you avoid duplicate records when a Salesforce sync retries?

Call Salesforce's upsert operation against an external ID field instead of separate create and update calls. A retried execution then re-sends the same external ID value, and Salesforce matches it to the record it already wrote, updating in place. A plain create call has no such match key, so a retry inserts a second record.

Should ecommerce order data sync to Leads or Opportunities?

It depends on what sales does with the data once it arrives. Leads suit prospects who haven't bought yet; existing customers placing repeat orders usually map to an Account, Contact and Opportunity (or a custom object) instead. Confirm this with whoever owns the Salesforce org before building the mapping, since it drives every downstream report.

What happens if n8n and Salesforce disagree on the same record?

Whichever system writes last wins, unless you build a conflict check into the workflow. A common pattern reads the record's last-modified timestamp before writing and skips or flags the update if Salesforce changed more recently than the source data, rather than silently overwriting a manual edit a sales rep just made.

Is Salesforce Bulk API 2.0 free to use?

Bulk API access comes with standard Salesforce API access rather than as a separate paid add-on, but it still draws from the same rolling API allocation as any other call. Confirm current inclusion and any edition restrictions on Salesforce's own pricing and API documentation, since packaging changes between editions and over time.

How often should an n8n Salesforce sync run?

Match the interval to how fast Salesforce needs the data, not to how often Shopify emits it. A webhook-triggered sync firing per order is more current but spends more of your API allocation than a scheduled batch every fifteen or thirty minutes. Start with a schedule, and move to webhooks only for objects sales actually acts on same-day.

Can you sync Salesforce data back into Shopify?

Yes, through the same n8n workflow pattern reversed: a Salesforce trigger or scheduled query feeding a Shopify node or HTTP Request call. It's less common than store-to-Salesforce sync, since Shopify's own data model doesn't have an obvious home for most Salesforce fields, and it needs the same external-ID discipline to stay idempotent.

What causes a Salesforce API limit error even below the daily allocation?

Salesforce enforces short-window concurrency and burst limits separately from the rolling daily allocation. A workflow that fires many calls in a few seconds can trip a limit like REQUEST_LIMIT_EXCEEDED while nowhere near its daily total, because the problem is the rate of calls, not the count. Spacing calls out with a throttle fixes this.

Do sandbox and production Salesforce orgs share the same API allocation?

No, each org, sandbox or production, has its own separate API allocation. Testing a sync heavily in a sandbox doesn't touch the production org's budget, which is exactly why it's worth building and load-testing the batching and throttle settings in a sandbox before pointing the workflow at production data.

What's the difference between an upsert and an update call in Salesforce?

An update call requires Salesforce's own record ID and fails if that ID doesn't exist. An upsert call matches on a field you choose, usually an external ID, and creates the record if no match exists or updates it if one does. Upsert is what makes a sync safe to retry without deduplication logic elsewhere.

Next step

Is this your ai agents & automation problem, or a symptom of another one?

Bring your numbers — the churn split, the decline rate, whatever your flows are earning — and we will tell you which of them is the expensive one.

Book a call →