
Every omnichannel project starts as a configuration question. Turn on the right modules, connect the store to the warehouse, done by Friday.
It never is. Not because Adobe Commerce is short of features, but because selling in two places at once forces you to answer questions the business has been quietly avoiding. Which system is allowed to be right about stock? What happens to a click-and-collect order when the shop floor has already sold the last one? Who owns the customer record?
Those are the parts that take the time.
Stock is one number, and everyone disagrees about it
Adobe Commerce ships Multi Source Inventory, which models stock per location and reserves it against orders. It is genuinely good. It is also not the hard part.
The hard part is that your warehouse system also thinks it knows the stock level, the POS thinks so too, and all three are right at different moments. A picker takes an item off a shelf and the ERP knows about it forty seconds before the website does. In those forty seconds somebody can buy it.
So the real decision is not technical. It is: which system is the source of truth, and what error margin is acceptable? A retailer selling ten units a day tolerates a sync lag that would be catastrophic for one selling ten thousand. Safety stock buffers, reservation windows, whether a channel may oversell and apologise or must refuse the sale — these are business rules, and they need settling before anyone writes an integration.
Get it wrong and the symptom is not an error in a log. It is a customer standing in your shop holding a collection slip for something that is not there.
One customer, several records
A single customer view sounds like a database join. In practice the same person is a guest checkout on the website, a loyalty number at the till, an email address in the newsletter tool, and a phone number in the support system. None of those keys match.
Adobe Commerce will hold the unified profile happily enough, once you have decided how identity resolution works. Deciding that is the work. Match on email and you merge two people who share a family address. Match too strictly and one customer stays split across four records, which is how somebody gets a "welcome, new customer" discount on their fortieth order.
No clean answer exists here. Only a threshold, which somebody has to own.
The integration layer is where projects overrun
Adobe Commerce exposes REST and GraphQL APIs, and the module system lets you extend more or less anything. The platform is rarely the constraint.
What overruns is everything on the other side of the wire. The ERP API was designed in 2009 and returns XML. The POS syncs on a schedule nobody can change. The loyalty platform has a rate limit that makes a full nightly sync impossible, so now you need deltas, and the vendor does not expose a changed-since filter.
Three habits reliably help.
Make every sync replayable. Systems go down mid-run, and a job you can safely execute twice is worth more than one that is fast.
Log what you sent and what came back, in full, for long enough to argue with a vendor about it. When stock is wrong at 9am on a Saturday the question is always "what did we send?", and the answer needs to exist.
Decide early what happens when a system is unreachable. Queue and retry, or fail loudly? Both are defensible. Silently dropping the message is not, and it is the default in more integrations than anyone likes to admit.
Consistent is not the same as identical
Teams often read "consistent experience" as "the app and the site should look the same". That is the least important part.
Customers notice when the promise changes between channels. A price that differs between the shelf label and the product page. A return accepted online and refused at the counter. Stock shown as available on a category page and gone at checkout, because those two pages ask different systems.
Fixing that is mostly about deciding which rules are global and which are legitimately per-channel, then enforcing the global ones in one place instead of reimplementing them in every frontend. Pricing, promotion eligibility and return windows belong in the commerce layer. Layout does not have to match at all.
What this looks like on a real project
Modelflight is a useful shape to think about. One brand, an online store serving a national audience, a customer service centre, and a separate hobby shop. With two physical locations and web customers across the country, the fulfilment rules could not be naive: which location ships what, and what the site is allowed to promise, had to be worked out rather than assumed.
That is the pattern in most of this work. The interesting decisions are about rules and ownership. The code follows.
Four questions before you scope anything
Which system is authoritative for stock, and by how much is each channel allowed to be wrong?
How do you decide two records are the same person, and who signs off on that rule?
What is the fallback when a downstream system is unreachable during checkout?
Which pricing and promotion rules are global, and which are genuinely per-channel?
Clear answers to those and the build is mostly plumbing. Without them, no amount of platform configuration saves the project. You just find the disagreements later, in production, with customers watching.
If you are working through this on an Adobe Commerce store and would rather argue it out with someone who has done it before, get in touch.