MODRACXKENNETH D'SILVA

← Archive & Insights

SAP Commerce Cloud & S/4HANA ERP Synchronization

Eleven thousand orders sat in a dead-letter queue for five days because a category was renamed in the ERP. The storefront never broke. Here are the patterns that prevent silent divergence between commerce and SAP.

By Kenneth D'SilvaReading Time: 22 min readCategory: Integrations & Systems

1. Eleven Thousand Orders in a Queue

I got a call on a Tuesday from a distributor whose ERP had stopped accepting orders from their storefront the previous Thursday. Nobody had noticed for five days, because the storefront was working perfectly — customers placed orders, got confirmations, and were charged. The orders simply never arrived in SAP.

Eleven thousand of them were sitting in a retry queue, most having exhausted their attempts and moved to a dead-letter table nobody monitored. Warehouse staff had spent the week wondering why it was quiet.

The cause was mundane: a product category had been renamed in the ERP, an inbound mapping expected the old value, and the integration rejected every order containing an item in that category. Which, because it was their largest category, meant nearly everything.

This is what ERP integration failure looks like in practice. Not a dramatic outage — a silent divergence between two systems that both believe they are correct, discovered days later by someone noticing an absence. The engineering that prevents it is not clever; it's mostly about idempotency, observability, and deciding in advance which system is allowed to be right.

This article covers the patterns that hold up, using SAP Commerce Cloud and S/4HANA as the concrete case, though nearly all of it applies to any commerce-to-ERP integration.

2. Decide Who Owns What, First

Every integration problem I have debugged traces back to two systems believing they own the same field. Before any technical design, write down the system of record for each domain, and make it a single answer per field rather than per entity.

A defensible default for most distributors and manufacturers:

DataSystem of recordDirection
Product master, SKU, unitsERPERP → Commerce
Marketing copy, images, SEOCommerce or PIMCommerce owns
List price, price listsERPERP → Commerce
Promotions, cart rulesCommerceCommerce owns
Contract pricing (B2B)ERPERP → Commerce, or live call
Stock quantityERPERP → Commerce
Customer account, credit limitERPERP → Commerce
Web registration, preferencesCommerceCommerce → ERP
OrderCommerce at capture, ERP afterCommerce → ERP, status back
Invoice, delivery, returnsERPERP → Commerce

The row that generates the most argument is product description, because merchandising wants to write copy and the ERP already has a material description. Resolve it by splitting the field rather than the entity: the ERP owns materialDescription, commerce owns marketingName, and they are different fields that never overwrite each other. Trying to have one field owned by two systems produces a loop where each overwrites the other on its own schedule, and the symptom — copy reverting overnight — takes a long time to diagnose.

The order row deserves care too. The storefront owns the order until it is accepted by the ERP; after that the ERP owns fulfilment state and commerce holds a read-only reflection. Getting this backwards, so the storefront tries to be authoritative about shipping status, produces the class of bug where a customer sees "shipped" for something the warehouse has not picked.

3. The Four Flows That Matter

Almost every commerce-ERP integration reduces to four exchanges with quite different characteristics. Designing them separately, rather than as one "sync", is the difference between something maintainable and something nobody wants to touch.

Product and price, ERP to commerce

High volume, low urgency, tolerant of latency. A nightly or hourly delta feed is usually right. The complexity is not the transfer, it's the transformation — ERP product data is modelled for finance and logistics, and storefront product data is modelled for browsing.

Key decisions: full load or delta. Full loads are simpler and self-healing, and they stop being viable somewhere around a few hundred thousand SKUs. Deltas are efficient and accumulate drift, because a message missed during an outage is missed permanently unless you also run periodic reconciliation. My preference is deltas hourly plus a full reconciliation weekly, which gets the efficiency and repairs the drift.

Stock, ERP to commerce

The one with genuine urgency, and the one most often built badly. Overselling is expensive and erodes trust faster than almost anything else on a storefront.

The instinct is to call the ERP for live stock on every product view. Don't. It couples storefront availability to ERP availability, it puts read load on a system sized for transactions, and it makes your product page as slow as your slowest backend call.

The pattern that works: cache stock in commerce, update it by event as movements happen, and reserve at the point of order rather than trusting a displayed number. Then treat the displayed quantity as advisory and the reservation as authoritative. A customer seeing "3 in stock" that turns out to be 2 is a minor annoyance; a customer completing checkout for something unavailable is a refund and a complaint.

For high-value or low-stock items, a live availability check at the add-to-cart or checkout step is a reasonable compromise — one call at a decision point rather than on every page view.

Order, commerce to ERP

Low volume, high value, and the flow where correctness matters absolutely. Every order must arrive exactly once. This is where idempotency lives, and it gets its own section below.

Status, ERP to commerce

Invoices, deliveries, tracking numbers, credit notes. Low urgency, and the flow most often left until last and then implemented badly. It matters because it drives customer-facing communication and self-service, and every gap here becomes a support call.

4. Customers, Accounts and the B2B Complication

Product and order flows get the attention; customer data is where B2B integrations actually get hard, because the two systems model a customer completely differently.

An ERP thinks in terms of a sold-to party, a ship-to party, a bill-to party and a payer, which may be four different entities with different addresses and different roles in the same transaction. A storefront thinks in terms of a person with a login. Mapping between those is not a field-level exercise; it is a modelling decision that has to be made deliberately.

The shape that works for most distributors: the ERP account is the organisation, and commerce holds users who belong to it. One company account may have a purchasing manager who can approve, three buyers who can order within a limit, and a finance contact who sees invoices but cannot place orders. Those users exist only in commerce; the ERP knows about the organisation and about which delivery addresses are permitted.

That split determines several things at once. Registration becomes a two-step process, because a new user requesting access to an existing account needs approval rather than instant self-service — which is a workflow, not a form. Address handling becomes constrained, because in most B2B settings a buyer may not invent a new delivery address; they choose from the ship-to parties the ERP allows, and free-text address entry is a fraud and a logistics problem.

The pieces that reliably cause trouble:

Credit status changes mid-session. A customer on stop in the ERP should not be able to complete an order, and the block may be applied while they are shopping. Check at checkout rather than only at login, and give support a clear message rather than a generic failure — "your account is on hold, please contact us" prevents a support ticket that starts with "the website is broken".

Account hierarchies. A parent company with subsidiaries that share pricing but not credit, or a buying group whose members order independently against a shared agreement. This is where a simple one-account-one-organisation model breaks, and retrofitting hierarchy later is expensive. Ask about it during discovery even when nobody raises it, because the answer is often "yes, for about a dozen of our largest customers", and those dozen are usually a substantial share of revenue.

Deleted or merged accounts. ERPs merge duplicate customer records as routine housekeeping. Commerce has users pointing at the old identifier, who then cannot log in or cannot see their order history. Handle merges explicitly with a mapping table rather than assuming identifiers are permanent.

5. Idempotency, Which Is Non-Negotiable

Any network call can fail after the work was done but before the acknowledgement arrived. Your integration cannot distinguish "the ERP never received this order" from "the ERP created the order and the response was lost." If your retry logic assumes the former, you will eventually create duplicate orders, and duplicate orders mean duplicate shipments and duplicate charges.

The fix is that the commerce side generates a stable, unique key for each order and the ERP side treats a repeat of that key as the same order rather than a new one.

// Commerce side: the key is generated once, at order placement, and never regenerated
public class OrderExportPayload {
    private final String idempotencyKey;   // e.g. "WEB-2026-000184213"
    private final String orderCode;
    private final Instant placedAt;
    private final int attempt;

    // The key must derive from the order, not from the attempt.
    // A UUID generated per retry is the classic mistake — it defeats
    // the whole mechanism while looking correct.
    public static OrderExportPayload of(OrderModel order, int attempt) {
        return new OrderExportPayload(
            "WEB-" + order.getCode(),
            order.getCode(),
            order.getCreationtime().toInstant(),
            attempt
        );
    }
}

On the ERP side, the receiving service checks whether that key has been seen and returns the existing document rather than creating a second one. If your ERP integration layer cannot enforce this, put a deduplication table in the middleware and enforce it there — it is worth building even as a workaround.

Two related properties worth designing in at the same time:

Retry with backoff and a ceiling. Exponential backoff with jitter, so a recovering ERP is not immediately flattened by a thundering herd of retries. And a maximum, after which the message goes to a dead-letter queue rather than retrying forever.

A dead-letter queue somebody watches. The distributor at the top of this article had one. It worked exactly as designed. Nobody had ever looked at it. A dead-letter queue without an alert is a place where problems go to be forgotten quietly, which is worse than no queue at all because it creates the impression of a safety net.

// Alert on depth and on age — depth alone misses the slow leak
if (deadLetterQueue.depth() > 0) {
    Duration oldest = Duration.between(deadLetterQueue.oldestMessageTime(), Instant.now());
    if (oldest.toMinutes() > 15) {
        alerting.page("order export dead-letter: " + deadLetterQueue.depth()
            + " messages, oldest " + oldest.toMinutes() + "m");
    }
}

6. Synchronous or Asynchronous

A decision that shapes everything downstream, and the wrong choice is difficult to reverse.

Synchronous — the storefront calls the ERP and waits. Simple to reason about, immediate confirmation, and it makes your checkout as available as your ERP. That last clause is the problem. ERPs go down for batch windows, month-end, and upgrades, and a synchronous design means your checkout goes down with them. It also makes checkout as slow as the ERP call, which on a busy S/4HANA system is not always fast.

Asynchronous — the storefront accepts the order, queues it, and confirms to the customer immediately. The ERP consumes at its own pace. Resilient, faster, and the right default. The cost is that "order accepted" and "order in ERP" become different states, and you have to design for the gap: what the customer is told, what happens if the ERP later rejects it, and how support sees the difference.

I use asynchronous for order submission essentially always, with two exceptions where a synchronous call is genuinely needed at the decision point rather than after it:

Credit limit checks for B2B. If a customer might exceed their limit, you need to know before you take the order, not after. A synchronous call at checkout with a short timeout and a defined fallback — usually "accept and flag for manual review" rather than "reject" — is the right shape.

Contract pricing. B2B pricing that depends on customer-specific agreements often can't be replicated in commerce faithfully. A live call with aggressive caching per customer-material pair works; recalculating the ERP's pricing logic in commerce does not, and every attempt I've seen has drifted within a year.

The general principle: synchronous calls belong where a wrong answer changes the customer's decision. Everywhere else, queue it.

7. Integration Middleware

SAP's own tooling for this is SAP Integration Suite, and on a Commerce Cloud project you will likely be using it or something equivalent. The choice matters less than the discipline, but a few observations.

Put transformation in one place. The recurring failure is mapping logic spread between the commerce extension, the middleware, and an ERP user-exit — because each layer added a small fix under time pressure. Debugging then requires tracing a field through three systems owned by three teams. Pick the middleware as the transformation layer and keep the others thin.

Version your contracts. The interface between commerce and middleware is an API, and it deserves the same treatment as any other: a schema, a version, and a deprecation path. Integrations that break on ERP upgrades are usually integrations with no contract, where both sides assumed the other would not change.

Keep the payloads flat and explicit. Deeply nested structures mirroring internal models make every change a coordinated release. A flat, explicit contract that names what it needs survives refactoring on both sides.

Log correlation identifiers everywhere. One identifier that follows an order from storefront through middleware to ERP document number. Without it, investigating "where did order 184213 go" means searching three log systems by timestamp and hoping.

MDC.put("correlationId", order.getCode());
MDC.put("customerId", order.getUser().getUid());
try {
    exportService.send(payload);
    log.info("order exported, erpDocument={}", response.getDocumentNumber());
} finally {
    MDC.clear();
}

8. Getting the Catalogue Load Right

Catalogue synchronisation is the flow that consumes the most engineering time on these projects, and the difficulty is rarely the transfer. It is that ERP product data is not storefront product data and the gap has to be closed by someone.

An ERP material master is modelled for procurement, costing and logistics. It has a material number, a description written by whoever set it up, base unit of measure, weight, and a classification hierarchy designed for reporting. What it usually lacks is anything a customer would want to read, images, or a structure that maps onto how people browse.

Three decisions to make explicitly.

Which attributes actually cross. The instinct is to replicate everything because the mapping is mechanical and storage is cheap. Resist it. Every attribute is a field to maintain, a column to index, and a thing that can be wrong. The useful test is whether any template renders it or any query filters on it; if neither, it does not need to be in commerce. Cutting a replicated set from 140 fields to 60 is a normal outcome of asking that question honestly, and it makes every subsequent load faster.

How units of measure translate. The ERP sells in boxes of 250; the storefront shows a price per unit; the warehouse picks in cases. Getting this wrong produces order quantities that are out by a factor of the case size, which is the kind of error that ships pallets to people who wanted one item. Model the conversion explicitly, test it with the awkward products, and be suspicious of any material where the base unit and the sales unit differ.

Where the browsing structure comes from. ERP classification hierarchies are built for internal reporting and make poor navigation. Most projects end up with a commerce-owned category tree, mapped from ERP classification but curated separately. That is more work and it is the right answer — merchandising needs to reorganise navigation without a change request to the ERP team.

A practical note on deltas. Whatever change-detection the ERP offers, verify what it actually reports. Some systems flag a material as changed when any field changes, including ones you do not replicate, which produces a delta feed that is nearly a full load. Filtering on the fields you care about, on the ERP side if possible, can shrink a feed by an order of magnitude.

9. Where Commerce Cloud Specifics Bite

A few things particular to SAP Commerce Cloud that shape integration design.

ImpEx for bulk loads. The native import format is genuinely efficient for large catalogue loads and awkward for anything conditional. It's the right tool for a nightly product feed and the wrong one for order status updates. Don't stretch it past its purpose because it's familiar.

The type system is a schema. Extending item types is normal and adding attributes is cheap, but every attribute you add to a heavily-indexed type has a cost at load time. Be deliberate about what you replicate from the ERP — teams routinely mirror fields nobody ever reads because the mapping was easy.

Solr indexing is a separate concern from data sync. An updated product is not a searchable product until the index catches up. On a large catalogue, a full reindex is measured in hours, so plan for partial indexing on the delta feed and full reindexing on a schedule. The bug this prevents: a price updates in the database and the search results keep showing the old one, which looks like a sync failure and is actually an indexing lag.

Cloud deployment windows constrain your batch schedule. Commerce Cloud deploys and ERP batch windows both take capacity, and if they overlap you get failures that look intermittent and are actually scheduled. Map the two calendars against each other once, and you'll explain several recurring incidents.

Cronjobs need singleton protection. In a clustered environment, a cronjob that runs on every node instead of one will happily export every order several times. Commerce has mechanisms for this; use them, and verify in a clustered environment rather than on a developer machine where the problem cannot appear.

10. Performance, and Not Letting the ERP Set Your Page Speed

A storefront backed by an ERP has a standing temptation: the authoritative answer lives over there, so ask for it. Follow that instinct consistently and your product page inherits the latency and the availability of a system designed for batch accounting.

The discipline is to be explicit about which calls are on a customer's critical path, and to keep that list very short.

Never on a product listing page. A category showing 48 products must not make 48 calls, and must not make one call that fans out to 48 lookups either. Everything on a listing comes from commerce's own data, refreshed asynchronously. If a number is slightly stale on a listing, nobody is harmed.

Rarely on a product detail page. One call, at most, and only when there is a genuine reason — customer-specific pricing that cannot be pre-computed being the main one. Cache it per customer and material with a short lifetime, and set a timeout short enough that a slow ERP degrades to a fallback rather than holding the page.

Acceptable at cart and checkout. These are decision points where a wrong answer costs money, and where a customer will tolerate a moment's wait. Credit checks, final pricing, and availability confirmation belong here.

Two implementation details make the difference between this working and not.

Timeouts must be short and must have a defined fallback. An unbounded call to an ERP means a page that hangs rather than a page that degrades, and hanging is worse — the customer waits, gives up, and you have lost the session and generated load. Two seconds is generous for a pricing call; beyond that, fall back to list price and flag the order for review.

And a circuit breaker is worth the complexity on any synchronous path. When the ERP starts failing, continuing to send it traffic makes recovery slower and makes every affected page slow. Trip the breaker, serve fallbacks, and probe periodically until it recovers.

// Fail fast and degrade, rather than queue customers behind a struggling ERP
private final CircuitBreaker breaker = CircuitBreaker.of("erp-pricing",
    CircuitBreakerConfig.custom()
        .failureRateThreshold(50)
        .waitDurationInOpenState(Duration.ofSeconds(30))
        .slidingWindowSize(20)
        .build());

public Price priceFor(String customerId, String material, Price listPrice) {
    return Try.ofSupplier(
            CircuitBreaker.decorateSupplier(breaker,
                () -> erpClient.contractPrice(customerId, material)))
        .recover(t -> {
            metrics.increment("pricing.fallback");
            return listPrice.withFlag(PriceFlag.FALLBACK);
        })
        .get();
}

That FALLBACK flag matters as much as the breaker. It gives support a way to explain a price a customer queries, and it gives you a metric that tells you how often customers are seeing a degraded answer — which is the number that should drive whether the integration needs attention.

11. Reconciliation, the Part Everyone Skips

Event-driven sync drifts. A message is dropped during a deploy, a transformation silently fails on an edge case, a manual change is made directly in one system. None of these announce themselves, and the divergence accumulates until someone notices a symptom.

The fix is a scheduled comparison that treats divergence as an expected condition rather than an emergency.

-- Daily: orders placed on the storefront with no corresponding ERP document
SELECT o.code, o.creationtime, o.totalprice, o.erp_status
FROM orders o
WHERE o.creationtime >= NOW() - INTERVAL 7 DAY
  AND (o.erp_document_number IS NULL OR o.erp_document_number = '')
  AND o.status NOT IN ('CANCELLED', 'DRAFT')
ORDER BY o.creationtime;

Three reconciliations worth running, in order of value:

Orders without an ERP document, older than the expected latency. This is the one that would have caught the eleven thousand within an hour. Run it every fifteen minutes and alert on any result.

Stock divergence beyond a threshold. Compare commerce's cached quantity against the ERP's for a sample of SKUs daily. Small differences are normal in a moving warehouse; a systematic drift in one direction indicates a broken event stream.

Price divergence. Compare a sample of prices weekly. Price errors are commercially serious and, unlike stock, there is no legitimate reason for divergence.

Publish the results somewhere visible even when they're clean. A dashboard that shows zero divergence every day builds the habit of looking, so the day it shows something, someone notices.

12. Failure Modes and What to Do About Them

The ERP is down for a batch window. Expected and schedulable. Queue orders, keep taking them, and surface an internal indicator rather than a customer-facing one. Customers don't need to know your ERP is in month-end close.

The ERP rejects an order. The interesting case, because the customer has already been told it succeeded. Decide the policy before you need it: which rejection reasons are automatically retryable after a data fix, which need human review, and what the customer is told and when. A rejection that sits silently in a queue is the eleven-thousand-order failure in miniature.

Partial success. The order is created in the ERP but the confirmation to commerce fails, so commerce retries and idempotency prevents a duplicate — but commerce still doesn't know the document number. Handle it with a reconciliation lookup by idempotency key rather than by re-sending: ask the ERP "do you have this key" and record the answer.

Reference data drift. The failure from the opening of this article. A category, unit of measure, payment method, or country code changes in one system and the other's mapping is stale. Two defences: validate reference data on a schedule rather than trusting it, and fail loudly on an unknown value instead of dropping the record.

Clock skew and time zones. An ERP running in one time zone and a storefront in another, with a batch window defined in local time and a daylight-saving transition. Store everything in UTC, convert at display, and be suspicious of any bug that appears twice a year.

13. Making It Observable

The theme running through every failure above is that nobody knew. Three things worth instrumenting, and none of them are expensive.

End-to-end latency per flow. Not "did the call succeed" but "how long from order placed to ERP document created". Chart the distribution. A rising p95 is an early warning that something is degrading before it fails outright.

Volume against expectation. Orders exported per hour, compared to a rolling baseline for that hour and weekday. Alert on a significant drop rather than on errors, because the worst failures produce no errors — they produce silence. This single alert catches the majority of integration incidents I have seen.

Queue depth and message age. Depth alone is misleading; a queue can be shallow and stuck. Age of the oldest unprocessed message is the honest metric.

And one process item that matters more than any dashboard: decide who is paged when order export stops. Integration sits between two teams and frequently belongs to neither, which is precisely how a queue accumulates for five days.

14. Running It After Go-Live

Integrations are not delivered, they are operated, and the handover is where a good build becomes a bad system.

Write the runbook while you still remember. Not documentation of the design — instructions for the specific things that will happen. How to replay a dead-lettered order. How to trigger a manual reconciliation. What to check first when order volume drops. Who to call at the ERP end out of hours. Half a page per scenario, written during the project, is worth more than a design document nobody opens.

Give support a view into the integration. The most common escalation is "where is my order", and the answer requires knowing whether it reached the ERP. If answering that means asking an engineer to grep a log, every instance becomes a ticket. A simple admin screen showing export status and ERP document number per order removes an entire category of escalation and pays for itself within weeks.

Rehearse the failure. Once, in a controlled window, disconnect the ERP and confirm the behaviour: orders queue, the customer sees a normal confirmation, alerts fire, and when the connection returns the backlog drains without duplicates. Then do it again after any significant change. This is the single most valuable test in the whole project and it takes an afternoon.

Review the reference data mapping on a schedule. Country codes, payment terms, units of measure, category codes, shipping methods. These change in the ERP without anyone thinking to tell the commerce team, because from their side it is routine master-data maintenance. A quarterly check that every mapped value still exists on both sides prevents the failure this article opened with.

Watch the boring numbers. Export volume by hour against baseline, queue age, reconciliation results, and the ratio of retries to first-attempt successes. That last one is an underrated leading indicator — a rising retry rate usually precedes an outright failure by days, and it is the signal that lets you fix something before it becomes an incident.

15. A Project That Went Well

A janitorial supplies distributor, roughly 180,000 SKUs, S/4HANA, moving from a legacy webshop to Commerce Cloud. B2B with contract pricing and credit limits, which is the harder end of this problem.

What we decided early. The ownership table came before any code, and took two workshops with commercial stakeholders rather than engineers. That felt slow and saved months — the marketing-copy-versus-material-description argument surfaced in week one instead of during UAT.

Order flow. Asynchronous with idempotency keys derived from the order code, exponential backoff, a dead-letter queue with a fifteen-minute age alert, and a reconciliation query every fifteen minutes for orders lacking an ERP document. The reconciliation caught two real incidents in the first year, both within twenty minutes.

Pricing. Live calls to the ERP for contract prices at cart level, cached per customer-material pair for fifteen minutes, with list price as a fallback if the call timed out and a flag so support could see when a fallback had been used. Nobody attempted to reimplement SAP's pricing logic, which was the single most important restraint of the project.

Stock. Event-driven updates into commerce, displayed as banded availability rather than exact counts — "in stock", "low stock", "on request" — which removed a whole class of complaint about numbers being slightly wrong, and reserved at order placement.

What went wrong. The catalogue load was the problem, not the orders. The initial full ImpEx import took eleven hours and blocked the deployment window. We moved to delta loads with a weekly full reconciliation, and cut the replicated attribute set from 140 fields to 60 after asking which ones any page actually rendered. Load time dropped to under an hour.

What I'd do differently. Build the reconciliation queries in week one rather than month four. They are twenty lines of SQL and they are the difference between finding a problem in fifteen minutes and finding it in five days. We built them after a near-miss, which is the usual and wrong order.

16. Questions That Come Up

"Should we sync in real time?" Only stock and, for B2B, credit and contract price. Everything else tolerates minutes or hours, and real-time coupling buys you an ERP outage becoming a storefront outage. Ask what decision the freshness changes; if none, batch it.

"Can we skip middleware and call the ERP directly?" You can, and it works until the first schema change, at which point transformation logic is embedded in your commerce extension and an ERP upgrade becomes a commerce release. A thin integration layer is worth its cost mostly as a place to absorb change.

"How do we handle an ERP upgrade?" Contract versioning and a compatibility window where the integration accepts both old and new shapes. Then migrate, then remove the old path. Big-bang cutovers of an integration are how you get an unplanned weekend.

"What about returns and credit notes?" Frequently the last thing built and the first thing customers ask about. The ERP owns them; commerce reflects them. Budget for it properly rather than treating it as a phase two that never arrives.

"Do we need a separate PIM?" If merchandising needs richer product content than the ERP holds — which is usually true for anything consumer-facing — then yes, and it sits between the two with clearly defined ownership. If you sell industrial parts by part number, the ERP plus a few commerce-owned fields is often enough.

"How do we test this?" A sandbox ERP with realistic data, contract tests against the interface schema so either side can change independently, and a rehearsed failure drill — take the ERP connection down deliberately and confirm orders queue, alerts fire, and recovery drains cleanly. That drill finds more than any amount of unit testing.

17. What Actually Prevents the Bad Week

Nearly everything in this article is ordinary engineering. Stable keys, bounded retries, a queue somebody watches, a scheduled comparison between two systems, and a clear answer to who owns each field.

None of it is specific to SAP, and none of it is difficult. It gets skipped because integration work is invisible when it succeeds and because it sits in the gap between the commerce team and the ERP team, where neither has clear ownership and both assume the other is watching.

There is a related organisational point worth making plainly. On most of these projects the commerce team is an agency or a product team measured on storefront delivery, and the ERP team is internal and measured on stability. Neither is rewarded for the integration working well, and both are blamed when it doesn't. That misalignment produces the specific behaviours you see on troubled projects: mapping logic quietly duplicated on both sides so neither has to wait for the other, alerts routed to whoever complained last, and a shared reluctance to touch anything after go-live. Naming a single owner with authority over the contract between the two systems fixes more integration problems than any amount of architecture.

So the highest-value thing you can do is not technical. Name an owner for the integration, give them the volume alert and the dead-letter alert, and make sure the reconciliation query runs. The distributor's eleven thousand orders were not caused by a hard problem. They were caused by a queue that worked perfectly and a dashboard nobody had opened.

Suggested & Related Reading

Explore related engineering guides from Kenneth D'Silva: