Table of Contents
“Where is my order?” drives about 33% of all ecommerce support tickets, according to Gorgias’s Ecommerce Customer Service Benchmark, and order status sits somewhere between 25% and 50% of total ticket volume at most companies. If you run Salesforce, that number hides an awkward detail. The answer seldom lives in Salesforce. It lives in the ERP.
So when you stand up a Salesforce customer portal and send customers to it, you have not deflected those tickets yet. You have moved them one click closer to a page that cannot answer the question. Closing that gap is an integration problem, and the choice you make in the first hour of design decides how much of your life you spend maintaining it.
The portal asks Salesforce a question only the ERP can answer
Order headers often do sit in Salesforce, because that is where sales works. Almost nothing else does. Fulfillment status, lot and serial numbers, promised ship dates, warehouse allocation, credit holds, and current on-hand quantity all live in NetSuite or Business Central or SAP or whatever system actually runs the warehouse.
A portal page that shows an opportunity and a closed-won date is not useful to a buyer who wants to know whether the 400 units they ordered shipped from the Ohio warehouse this morning. That buyer calls your support line, and your deflection numbers stay flat.
Two things follow. First, the portal needs data it does not own. Second, some of that data goes stale in minutes, which rules out the easy answer.
Decide first: replicate or federate
Before you write a line of Apex or open a middleware canvas, settle this one question. Does the ERP data come into Salesforce and live there, or does Salesforce read it where it sits?
| Replicate | ERP records are written into Salesforce objects on a schedule or on an event. The portal reads normal Salesforce records. |
| Federate | ERP records stay in the ERP. Salesforce reads them live through Salesforce Connect and External Objects, usually over OData. |
Replication gives you everything the platform does well. Reports work. List views work. Formula fields, sharing rules, Flow, and full-text search all work, because the data is just records. The cost is freshness and volume. You are now storing a copy of your ERP in Salesforce, paying for the storage, and answering for every row that drifts out of sync.
Federation gives you data that is correct at the moment of the page load, with no storage cost and no sync job to babysit. The cost is that External Objects are second-class citizens. You cannot run roll-up summaries against them. Reporting is limited. Every page view becomes a live callout to the ERP, so if the ERP is slow at month-end, your portal is slow at month-end.
Neither answer is right for all of the data, which is the part most teams miss.
The pattern that actually holds up: split by volatility
Sort the data by how fast it changes, then route it.
Replicate the stable history. Closed orders, past invoices, shipped quantities, and signed documents are finished facts. They do not change, so a nightly or hourly sync into custom objects is fine. Your customers get fast pages and real reporting on their own order history, and you get to use standard sharing.
Federate the live numbers. On-hand inventory, available-to-promise, open backorder dates, and current credit status change during the day. Read these live. A number that was true at 2am is worse than no number, because the customer will plan around it and then call you when it turns out to be wrong.
That single split resolves most of the architecture. The stable majority of your data behaves like Salesforce data. The small volatile slice stays where it is accurate.
The four pieces you will configure
Once the split is settled, wiring a Salesforce customer portal to the ERP is mostly platform configuration rather than code.
- Named Credentials. Put the ERP endpoint and its auth in a Named Credential, not in a custom setting and not in Apex. You get the callout allowlist, credential rotation without a deploy, and per-user or per-named-principal auth. If the ERP supports OAuth, use the External Credential with an OAuth flow rather than storing a static key.
- Salesforce Connect and External Objects. For the federated slice, define the external data source and let the platform surface ERP entities as External Objects, named with the __x suffix. Link them to real Salesforce records using indirect lookups, which match on an external ID field rather than a record ID. This is how an ERP customer number finds its way to an Account without you writing a join.
- Platform Events for push. Polling the ERP on a timer is wasteful and always a little bit late. If the ERP can emit on change, have it publish a Platform Event and subscribe from Flow or a trigger. Shipment confirmations and backorder date changes are a good fit. The portal then reflects a change seconds after it happens, and you are not calling an endpoint every five minutes to be told nothing happened.
- Middleware, when the mapping is real work. A direct point-to-point connection is fine for a handful of entities with clean field matches. Once you are reconciling units of measure, multiple warehouses, tax treatment, or several ERP instances from an acquisition, put an integration layer in between. MuleSoft, Boomi, or a lighter broker all work. The deciding factor is not scale; it is how much transformation logic you are about to hide inside a trigger.
The permission layer almost everyone gets wrong
This is the part that reaches production and then fails the security review.
External user access to External Objects does not follow the sharing model you are used to. Standard sharing rules, role hierarchy, and criteria-based sharing do not apply to __x objects the way they apply to custom objects. Access is governed by object and field permissions on the profile or permission set, plus whatever filtering you build into the query or the external data source itself.
Read that again if you are exposing inventory through a portal, because the practical consequence is sharp. If you grant read on an external inventory object and do not constrain the query by the logged-in user’s account, you have built a page where any authenticated customer can request another customer’s data by changing a parameter. The portal looks fine in your admin session. It is wide open to your customers.
Constrain at the source. Filter the ERP query by the account identifier resolved from the running user, validate that identifier server-side, and never accept it from the client. Then test the portal as a real external user, not with your own permissions.
Two more items in the same category. Field-Level Security needs an explicit pass over every federated field, because cost, margin, and internal notes often ride along in the same ERP entity as the quantity you meant to show. And if you use per-user authentication to the ERP, every portal user needs ERP credentials, which is usually the wrong model. Named principal with server-side filtering is almost always what you want for a Salesforce customer portal.
Watch the limits before they watch you
Federated reads are callouts, and callouts are governed.
A portal page that renders six External Object components is making six round trips per view. Multiply by concurrent users at 9 am on a Monday. The failure mode is not an error in your logs; it is a portal that takes eleven seconds to paint and a customer who goes back to calling support.
Build three defenses in from the start. Cache the slow-changing-but-not-static values in Platform Cache with a short time to live, so a quantity gets read once per minute rather than once per component. Use the Composite API to collapse several reads into one request. And set an explicit timeout on the data source so a stalled ERP degrades one component instead of hanging the page.
Also instrument it. Log callout duration and failure rate from day one, because the first sign of trouble in a federated portal is latency creep, and nobody notices latency creep without a graph.
What to settle before you build
A short list, worth answering on paper first.
- Which fields must be live to the second, and which can be an hour old? This is the replicate-or-federate decision, written down per field.
- Does the ERP expose OData, a REST API, or neither? Neither means middleware is not optional.
- What is the account identifier that ties an ERP customer to a Salesforce Account, and is it populated on every record today?
- Can the ERP emit change events, or are you polling?
- What happens to the portal page when the ERP is down for patching?
That last one decides whether you show a stale cached number with a timestamp or an empty state. Pick deliberately. A blank panel generates a support ticket just as reliably as a wrong number does.
The short version
The order status question is answerable in a portal, but only if the portal reads from the system that knows the answer. Replicate the history, federate the live numbers, push the field access decision all the way down to the ERP query, and put a graph on your callout latency.
At CRMJetty, we have watched this play out across Salesforce, Dynamics 365, SugarCRM, and SuiteCRM deployments, and the split by volatility is the one pattern that holds in every one. Teams that start with a nightly sync for everything usually end up rebuilding the inventory half within a year. Pre-built portal platforms take a similar approach, which is why a Salesforce Customer Portal built for this handles order history and live stock through different paths rather than one sync job. The same constraint shows up even more plainly in ERP-connected portals for Business Central, where almost nothing the customer wants to see originates in the CRM at all.