FastNet
Technical·10 min read

Omnichannel integration without rewriting your grocery chain's ERP

How to connect e-commerce, mobile app, marketplaces and bank reconciliation to a fifteen-year-old SAP IS-Retail or Oracle Retail without migrating it. The pattern that actually works in production: event-driven middleware, not big-bang.

Published: May 3, 2026·By: Eddy

If you run technology for a grocery chain with fifty or more stores in Latin America, you've already had this conversation three times this year. It starts with someone — an outside consultant, a new vendor, a freshly-promoted VP of operations — telling you "the ERP is old, we need to migrate it." It ends with you doing mental math about what it costs to touch the system that processes payroll, inventory, accounting and bank reconciliation across fifty stores, and answering the same thing you answered the previous two times: "yes, but not now."

You're not wrong. You're applying judgment.

Rewriting the ERP of a fifty-store grocery chain isn't a technical decision — it's a business continuity decision that rarely has the answer it appears to. The right question isn't how do we migrate the ERP. It's how do we add a modern layer without touching what already works. This post is about how to do that well.

The shape of the problem

Let's map reality first. The chain I'm describing typically runs this stack:

  • A legacy ERP — SAP IS-Retail, Oracle Retail, JD Edwards or a twelve-year-old custom build — that's the source of truth for inventory, pricing, accounting and master data.
  • A multi-store POS that syncs with the ERP in batches, usually at end of day.
  • An owned e-commerce running Magento, Shopify, VTEX or a hand-built Next.js, keeping its own "almost"-synced inventory.
  • A mobile app (Android + iOS) consuming the same e-commerce backend.
  • Integrations with local marketplaces — PedidosYa, Hugo, Uber Eats — each with its own catalog format, its own order webhook, its own stock policy.
  • Bank reconciliation at end of day with multiple banks, often handled via flat files downloaded manually each morning.
  • A cold chain system with IoT sensors reporting to whatever proprietary platform the refrigerator vendor sells.
  • A handful of Excel sheets holding up critical processes nobody wants to ask about anymore.

The problem isn't that any one of these systems is bad. The problem is that each was chosen for valid reasons in its moment, and none was designed assuming it would have to talk to the other seven in real time.

The silent trap: nobody wants to rewrite the ERP, but everyone wants "the ERP to respond in real time to e-commerce." That isn't solved by migrating — it's solved by building the space between the systems correctly.

The pattern that works: event-driven middleware

The right answer has been written in enterprise architecture books for twenty years and gets ignored every time a new vendor shows up promising a "unified platform." Here it is:

Don't integrate systems. Integrate events.

Instead of having e-commerce ask the ERP for stock every time a customer loads their cart, have the ERP publish an inventory.updated event whenever its stock changes, and let every other system — e-commerce, app, marketplaces — listen for that event and keep its own copy of inventory updated reactively.

This is event-driven architecture with CDC (change data capture), and it has three benefits a senior CTO recognizes immediately:

  1. The ERP doesn't notice. No real-time query load is added. It keeps doing what it does, at its own pace, in its own windows.
  2. Modern systems don't depend on ERP availability. If the ERP is in maintenance one Sunday, e-commerce keeps running on its last valid copy of inventory.
  3. Any future system plugs in without touching anything existing. If Hugo Express comes online in six months and needs the catalog, it subscribes to the event stream. The ERP isn't modified. E-commerce isn't modified.

Our default stack for grocery enterprise in Latin America:

EVENT-DRIVEN MIDDLEWARE STACK — DEFAULT GROCERY
  • Event bus · Apache Kafka (Confluent Cloud) or Redpanda
  • CDC · Debezium for Oracle/SAP HANA, ksqlDB for transforms
  • Adapter layer · NestJS / FastAPI with idempotent consumers
  • Schema registry · Confluent Schema Registry with Avro
  • Observability · OpenTelemetry → Grafana Cloud
  • Hosting · AWS or GCP, us-east or sa-east region

There are valid alternatives — RabbitMQ Streams, AWS EventBridge + SNS, Azure Service Bus — and the choice depends on compliance, operating budget, and where the rest of the infrastructure already lives. The architectural decision that matters isn't which bus. It's that there is one.

What it looks like in code

To make it concrete: here's a consumer for the order.created event syncing a PedidosYa order into the POS and ERP in parallel, without coupling either to the other.

// apps/integration-layer/src/consumers/order-created.ts
import { KafkaConsumer } from '@nestjs/kafka';
import { Idempotent } from '@/lib/idempotency';
import { ErpClient } from '@/clients/erp-sap';
import { PosClient } from '@/clients/pos';
import { OrderEvent, OrderEventSchema } from '@/schemas/order';
 
@KafkaConsumer({ topic: 'orders.created.v1', groupId: 'erp-pos-sync' })
export class OrderCreatedConsumer {
  constructor(
    private readonly erp: ErpClient,
    private readonly pos: PosClient,
  ) {}
 
  @Idempotent({
    key: (event: OrderEvent) => `order:${event.orderId}:erp-pos-sync`,
    ttl: '24h',
  })
  async handle(rawEvent: unknown) {
    const event = OrderEventSchema.parse(rawEvent);
 
    // Both systems receive the event in parallel.
    // If one fails, the other does NOT roll back — Kafka retries only the failed one.
    const [erpResult, posResult] = await Promise.allSettled([
      this.erp.createSalesOrder({
        externalRef: event.orderId,
        channel: event.channel, // 'pedidosya' | 'web' | 'app' | 'pos'
        store: event.storeCode,
        items: event.items,
        total: event.total,
      }),
      this.pos.registerOnlineOrder({
        orderId: event.orderId,
        store: event.storeCode,
        items: event.items,
      }),
    ]);
 
    if (erpResult.status === 'rejected') throw erpResult.reason;
    if (posResult.status === 'rejected') throw posResult.reason;
  }
}

Three details in this code separate an integration that survives from one that doesn't:

1. The @Idempotent decorator. If Kafka redelivers the event — and it will, because Kafka guarantees at-least-once, not exactly-once — the second attempt detects that the orderId was already processed and duplicates nothing. Without idempotency, on Mother's Day you'll be billing the same orders twice and decrementing phantom inventory.

2. Promise.allSettled, not Promise.all. If the POS is down for 90 seconds while the ERP responds, the order lands in the ERP. When the POS comes back, Kafka redelivers the event — idempotency detects the ERP already processed it, only the POS gets the insert. Zero rollbacks. Zero inconsistent state.

3. Validated schemas (OrderEventSchema.parse). Any malformed event fails fast and goes to a dead letter queue. It doesn't corrupt downstream systems.

A pragmatic 90-day rollout

The most common mistake of chains attempting this is wanting to do the full cutover on day one. It doesn't work. The way that works is by event domain, in order of lowest to highest risk:

Weeks 1-2 — Real technical discovery. Not documented, real. You map flow by flow what happens today, where the Excel sheets live, who manually re-syncs what every morning. The rule: if you can't draw the flow on a whiteboard in front of the operations director and have them nod, you don't yet understand the system.

Weeks 3-5 — Shadow event bus. You bring up Kafka, hook Debezium into the ERP, start publishing inventory.changed, price.updated, master.updated events. Nobody consumes them yet. You're measuring volume, latency, integrity. If the ERP generates 240,000 inventory changes per day, you learn that now, not in production.

Weeks 6-8 — First production consumer. Pick the lowest-risk case. Recommendation: sync the ERP catalog into a new catalog service serving e-commerce. If it fails, e-commerce holds a 12-hour-stale catalog — not ideal, but it doesn't break anything operational.

Weeks 9-12 — Omnichannel orders. Only now. Order events from e-commerce, app, PedidosYa, Hugo all go to the same orders.created.v1 topic. One consumer routes to the ERP. Another consumer routes to the POS at the dispatching store. End-of-day bank reconciliation — that consumer ships in the next phase, not this one.

By day 90 you have a functional middleware, real production integrations, and zero lines modified in the ERP. By day 180 you have automated bank reconciliation and demand forecasting using the order stream as input. By day 365 you can evaluate whether migrating the ERP makes sense — and the answer is probably still "not yet," but for different reasons.

Honest trade-offs and costs

None of this is free. Honest costs for a fifty-store chain with meaningful web traffic:

What it costs
What it saves
What it costsWhat it saves
MONTHLY INFRAUSD 2,000–8,000/month (managed Kafka + adapter services + observability).Zero ERP migration cost (USD 800k–4M avoided).
TEAM1 senior platform engineer + 1 mid + 1 part-time SRE during the build.No retraining for operations team. They keep using the ERP as always.
LATENCYEventual consistency: e-commerce sees inventory with 2–8 second lag.Acceptable in 99% of grocery cases. Critical only in seasonal peaks, mitigated with carry-cart cache.
DEBUGGINGWhen something fails, you correlate traces between ERP, bus, consumer, target. Without serious observability, you go insane.With observability done right, you find the offending event in under 10 minutes.
GOVERNANCESchemas, versioning, contracts between teams. More discipline.First new consumer plugs in in 2 days, not 3 months.

The most underestimated trade-off is eventual consistency. When a customer pays online at 7:42pm on Mother's Day, ERP inventory decrements 1–6 seconds later. In that interval, another customer can see the product available and buy it. This is mitigated with a transient inventory lock in the e-commerce catalog service — you decrement locally, wait for ERP confirmation, revert if rejected. It's an extra layer of complexity. Worth knowing about up front, not discovering in production.

Common mistakes we see in Latin American grocery

Five we've seen repeat in serious chains:

1. Bidirectional real-time inventory sync from day one. Kills the ERP. The chain's ERP wasn't designed to respond to 800 queries per second from a Black Friday surge. CDC-based eventual consistency is the answer — not optional, structural.

2. Processing orders without idempotency. It will fail on the most expensive day of the year. Period. There's no kind version of this lesson.

3. No dead letter queue. When a malformed event enters the system, with no DLQ it stays in an infinite retry loop and saturates the cluster. With a DLQ, it goes to a separate queue a human reviews once a day.

4. Confusing middleware with monolith. Some consultants will sell you a "unified integration platform" that's actually a monolith dressed up as a bus. If the tool doesn't support multiple independent consumers per topic, it's not event-driven — it's a wrapper over cron jobs.

5. Skipping observability because it looks like overhead. It's the opposite. Without distributed tracing, your first incident shows you why OpenTelemetry isn't optional when six systems are talking to each other. We cover this in detail in Observability 24/7 in grocery chains with 50+ stores.

If your ERP is fifteen years old

If your ERP is fifteen years old and your e-commerce is three, and the most recurring conversation with your team is "how do we make them talk better" — the right path is to build the space between them well, not to fuse them. It's cheaper. It's more reversible. And, most importantly, it doesn't put fifty stores' operations at risk while you build it.

That kind of project is exactly where our engineering retainer belongs: 90 days to take the first domain to production, 180 to have three domains plus automated bank reconciliation, and a dedicated team along the way. We don't sell platforms — we design and operate the integration.

If this sounds like your situation, let's talk. Thirty minutes. I call you. No pitch, no long form — just figuring out whether it makes sense to keep talking.

E
By

Eddy

Engineer since 1997. Founder of FastNet. I build software for companies that already went through agencies and learned what generic costs. I live between Los Angeles and Central America, and from there I watch the same problem: how chains running 24/7 wire five systems that were never built to talk to each other.

Does this resonate? Let's talk →

ALSO FOR YOUR DESK

Omnichannel integration without rewriting your grocery chain's ERP · FastNet