Acumatica · Integration

Acumatica Shopify Integration — Patterns That Work

How to integrate Acumatica with Shopify — bidirectional inventory, order, and customer sync, with the event-driven architecture that scales beyond a few hundred orders a day.

John Kihiu12 min read

Shopify is the one e-commerce platform where I tell clients to think hard before building anything custom, because Acumatica ships a native Shopify connector as part of the Retail (formerly Commerce) Edition. It's a real, maintained product — not a marketplace afterthought — and for the standard flows it will outlast anything you or I write. The interesting engineering questions are all about where the connector's boundaries are and what pattern to use once you cross them.

What the native connector actually covers

Out of the box, the connector syncs stock items, item availability, prices, customers, orders (including POS orders from Shopify POS), payments with fee handling, refunds, and shipment/fulfillment status back to Shopify. It runs on a schedule or near-real-time, has an entity-by-entity sync configuration screen, and — critically — has a processing log that shows you exactly why a given order failed to import. If your requirement fits that list, use it. Full stop.

Where I've had to step outside it on real projects:

Pattern 1: extend the connector, don't replace it

The connector's processors are graphs, which means they're extensible like everything else in Acumatica. The cleanest customization I've shipped in this space was a PXGraphExtension on the order processor that overrode order-type mapping: read the Shopify order's tags out of the external record, pick the Acumatica order type, done. Fifty lines, survives connector upgrades, and the client keeps the connector's retry machinery, logging, and support path.

C#
public class SPSalesOrderProcessorExt : PXGraphExtension<SPSalesOrderProcessor>
{
    public delegate void MapOrderDelegate(SalesOrderData external, SalesOrder local);

    [PXOverride]
    public void MapOrder(SalesOrderData external, SalesOrder local,
        MapOrderDelegate baseMethod)
    {
        baseMethod(external, local);
        // Route wholesale-tagged orders to a dedicated order type
        if (external.Tags?.Contains("wholesale") == true)
            local.OrderType = new StringValue { Value = "WS" };
    }
}

Before writing an extension, check the connector's own mapping UI — the Entities screen supports formula-based field mappings (the same expression language as import scenarios), and a surprising number of "we need custom code" requests are actually a one-line mapping formula.

Pattern 2: the sidecar service for what the connector can't do

When the requirement is genuinely outside the connector — say, listening to Shopify webhooks the connector ignores, or pushing data Shopify-ward on a trigger the connector doesn't have — I run a small sidecar service (Laravel, in my case) that talks to both APIs. The rule that keeps this sane: the sidecar never touches entities the connector owns. If the connector syncs orders, the sidecar does not also write orders, or you will spend your life diagnosing which system clobbered which field.

On the Acumatica side the sidecar uses the contract-based REST API with its own endpoint and its own API user, so its traffic is distinguishable in the request profiler. On the Shopify side, remember the leaky-bucket rate limit (2 requests/second on the REST Admin API for standard plans) — batch your reads with bulk operations on the GraphQL API when you're syncing catalogs of any size.

The gotchas that actually cost me time

Guest checkout customers. Shopify happily takes orders with no customer account. Decide up front whether these become one generic "Shopify Guest" customer in Acumatica (clean AR, useless CRM) or individual customers (noisy AR, dedupe headaches). For most B2C volumes, the generic customer plus capturing the real email on the order contact is the right trade.

SKU discipline. The connector matches on SKU. The day someone edits a SKU in Shopify admin "just to fix a typo," sync breaks silently for that item. I add a Generic Inquiry that lists Shopify SKUs with no matching InventoryItem and put it on the ops dashboard.

Refunds and partial refunds. Shopify refunds can include order-level discounts and shipping refunds that don't map 1:1 to line items. Test partial refunds with shipping included before go-live, not after the first angry month-end reconciliation.

Inventory oversell windows. Availability sync is periodic. If you run tight stock, shorten the availability export schedule and export available-for-shipping rather than on-hand quantity, or you'll sell units that are allocated to unshipped orders.

Don't sync your whole history on day one

First-time syncs against a store with years of history will import every historical order the filters allow. Set the earliest-order-date cutoffs on the store configuration screen before you press Prepare Data, or enjoy explaining ten thousand stale sales orders to the accountant.

Wrapping up

My decision tree for Shopify work: native connector for everything it covers, mapping formulas before code, graph extensions on the connector's processors when logic must live inside the sync, and a strictly-scoped sidecar service only for flows the connector has no opinion on. Every project where I've seen a team bypass the connector entirely and hand-roll the whole integration, they've ended up rebuilding — badly — the retry, logging, and reconciliation machinery Acumatica already ships.

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.