Acumatica · Integration

Acumatica Magento Integration — A Bridge Pattern

How to integrate Acumatica with Magento — the order, customer, inventory, and price flows, with the middleware architecture that handles the scale differences between the two platforms.

John Kihiu12 min read

Unlike Shopify, BigCommerce, or Amazon, Magento (Adobe Commerce) has no native connector in Acumatica's commerce framework. There are ISV connectors of varying quality, but on the two Magento projects I've done, the requirements — custom checkout attributes, multi-store views, a B2B price matrix — pushed us to build the integration ourselves. This post describes the architecture that survived: a bridge service between the two APIs, rather than either system calling the other directly.

Why a bridge and not point-to-point

The tempting shortcut is direct: a cron in Magento pushes orders at Acumatica's REST API, or an Acumatica scheduled job polls Magento. I've inherited both, and they fail the same way — each system embeds knowledge of the other's data model, errors have nowhere to go except a log nobody reads, and a schema change on either side means redeploying code inside a production commerce platform or a production ERP.

The bridge is a small standalone service (mine are Laravel; the pattern is language-agnostic) that owns three things:

The order flow, end to end

Magento fires a webhook (or the bridge polls /rest/V1/orders with a searchCriteria filter on updated_at — polling is more reliable than Magento's eventing on busy stores). The adapter normalises the order into the canonical model, resolves mappings, and enqueues an "order.import" job. The Acumatica adapter then executes a PUT against the contract-based API:

HTTP
PUT /entity/eCommerce/24.200.001/SalesOrder HTTP/1.1
Host: erp.example.com
Authorization: Bearer <token>
Content-Type: application/json

{
  "OrderType": { "value": "SO" },
  "CustomerID": { "value": "MAGWEB01" },
  "ExternalRef": { "value": "M2-000012345" },
  "Details": [
    { "InventoryID": { "value": "WIDGET-BLU" },
      "OrderQty": { "value": 2 },
      "UnitPrice": { "value": 1450.00 },
      "ManualPrice": { "value": true } }
  ]
}

Two details in that payload carry the whole design. ExternalRef holds the Magento increment ID — the bridge checks for an existing order with that reference before creating, which makes the import idempotent; replaying a failed job can never duplicate an order. And ManualPrice: true tells Acumatica to keep Magento's price instead of re-deriving it from price lists — the store is the pricing authority for web orders, and forgetting this flag produces subtle penny-level mismatches that reconciliation will find months later.

The return path: shipments and stock

Going the other way, I trigger on Acumatica business events — shipment confirmed, inventory quantity changed — posting webhooks to the bridge, which translates and calls Magento's POST /rest/V1/order/:id/ship with tracking numbers, and PUT /rest/V1/products/:sku/stockItems/:id for availability. Business events beat polling here because shipment confirmation is the moment the customer is waiting on; a 15-minute poll interval is 15 minutes of "where's my tracking number."

For stock, resist syncing raw on-hand. Export available-for-shipping from a Generic Inquiry (the same GI can drive the business event), and consider a safety buffer per SKU for fast movers — Magento will oversell during the propagation window otherwise.

Magento's API has its own moods

Adobe Commerce REST calls can be slow under load and the integration tokens expire in ways the docs undersell. Give the Magento adapter the same retry-with-backoff treatment as the Acumatica one, and re-authenticate on 401 rather than assuming token lifetime.

Operating it: the part that decides success

The bridge earns its keep operationally. Every job's payload and every API response is stored, so "why didn't order 12345 come through" is a lookup, not an investigation. Failed jobs retry with exponential backoff and land in a dead-letter state after five attempts, and the dead-letter count is the one metric I alert on. A tiny admin screen lists dead letters with the raw error — usually a missing SKU mapping or a credit-hold customer — and offers requeue-after-fix. That screen is the difference between the client's ops team self-serving and every hiccup becoming my WhatsApp problem.

Wrapping up

A Magento–Acumatica integration is not hard because of either API; it's hard because two systems with different data models must agree forever while both keep changing. The bridge pattern isolates that disagreement in one deployable place: canonical model in the middle, idempotent writes keyed on external references, business events outbound, persistent queue with a dead-letter screen. It's more upfront work than a point-to-point cron — and roughly a tenth of the lifetime maintenance.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.