Skip to content
SystemSyncer
Menu

Guide · Architecture

Shopify ERP Integration Architecture: A Practical Guide

The components of a dependable Shopify-to-ERP integration — receivers, queues, workers, outbox, reconciliation, and monitoring — and when to use each pattern.

Last reviewed:
Reading time
6 min read
Advanced

Short answer

A dependable Shopify–ERP architecture separates receiving events from processing them. Webhooks land in a lightweight receiver that stores them in a durable queue; workers fetch current data from the GraphQL Admin API and write to the ERP idempotently; changes originating in the ERP leave through an outbox; and scheduled reconciliation repairs anything the event path missed. Scale each piece only when measurements say you need to.

Data flow Two-way integration with inbox, outbox, and reconciliation
  1. Shopify Webhooks
  2. Inbox Deduplicated events
  3. Workers Map
  4. Outbox ERP change events
  5. ERP System of record

Inbound events pass through an inbox table; ERP changes leave through an outbox. Both paths are idempotent and replayable.

Most Shopify–ERP integrations that fail in production do not fail because of a wrong API call. They fail because the architecture couples two systems with different availability, speed, and data models too tightly. This guide describes an architecture that tolerates those differences, explains why each component exists, and shows where you can simplify.

The forces the architecture must handle

Before choosing components, be clear about what you are designing against. These are documented properties of Shopify plus typical properties of ERPs:

  • Webhooks must be acknowledged within five seconds, are retried up to eight times over four hours, and the subscription is removed if failures persist. (Shopify documentation)
  • Webhooks can arrive out of order and more than once. (Shopify documentation)
  • The GraphQL Admin API is rate-limited by calculated query cost using a leaky-bucket model. (Shopify documentation)
  • ERPs are often slow, have maintenance windows, and reject data that violates business rules. (General experience; varies by system)
  • Business users will edit data in both systems. (Always.)

An architecture that ignores any of these will work in a demo and break during the first sale.

Reference architecture

The reference design has six components. Small projects can collapse several into one process, but the responsibilities stay the same.

1. Webhook receiver

A stateless HTTP endpoint that verifies the HMAC signature, writes the event to the inbox, and returns 200. No mapping, no ERP calls, no Shopify API calls. Its only job is to accept events quickly and never lose one.

Because it is tiny and bursty, it is a good fit for a serverless function or edge runtime. Alternatively, Shopify can deliver webhooks directly to Amazon EventBridge or Google Cloud Pub/Sub, which replaces the receiver with a managed service.

2. Inbox (durable event store)

A table or queue holding every received event with:

  • the webhook ID (unique, for deduplication),
  • topic, shop domain, and trigger timestamp,
  • the raw payload,
  • processing status, attempt count, next attempt time, and last error.

The inbox gives you three things you cannot get otherwise: replay after a bug fix, an audit trail, and decoupling from ERP downtime.

CREATE TABLE inbound_events (
  id            BIGSERIAL PRIMARY KEY,
  webhook_id    TEXT NOT NULL UNIQUE,
  shop          TEXT NOT NULL,
  topic         TEXT NOT NULL,
  resource_id   TEXT,
  triggered_at  TIMESTAMPTZ,
  payload       JSONB NOT NULL,
  status        TEXT NOT NULL DEFAULT 'pending', -- pending | processing | done | failed | dead
  attempts      INT NOT NULL DEFAULT 0,
  next_run_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  last_error    TEXT,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON inbound_events (status, next_run_at);

3. Workers

Workers claim pending events, process them, and update their status. Each worker type handles one workflow — orders, refunds, product updates — so that a bug in one does not block the others.

Two rules make workers safe:

  • Fetch, do not trust. Use the webhook as a signal and fetch the current resource from the GraphQL Admin API. This sidesteps out-of-order delivery: whatever arrives, you always write the latest state.
  • Make every write idempotent. Use the Shopify resource ID as an external key in the ERP so retries update instead of duplicating. See retries and idempotency.

With PostgreSQL, SELECT ... FOR UPDATE SKIP LOCKED lets several workers claim jobs safely without a separate broker:

UPDATE inbound_events SET status = 'processing', attempts = attempts + 1
WHERE id IN (
  SELECT id FROM inbound_events
  WHERE status IN ('pending', 'failed') AND next_run_at <= now()
  ORDER BY next_run_at
  LIMIT 20
  FOR UPDATE SKIP LOCKED
)
RETURNING *;

4. Per-resource serialization

Out-of-order processing is a bigger risk than out-of-order delivery. If two workers process two updates for the same order concurrently, the older one can finish last. Prevent this by serializing work per resource:

  • Partition jobs by resource ID so one worker owns a given order at a time, or
  • Take an advisory lock on the resource ID while processing, or
  • Compare the resource’s updatedAt against the last value written to the ERP and skip older versions.

The third option is the simplest and works well combined with “fetch, do not trust.”

5. Outbox for ERP-originated changes

Inventory changes, shipments, and price updates start in the ERP. The reliable way to publish them is the transactional outbox pattern: when the ERP (or a database trigger, or a change-data-capture process) commits a change, it writes a row to an outbox table in the same transaction. A publisher reads the outbox and calls Shopify.

This avoids the classic dual-write problem where the ERP commits a stock change but the process crashes before notifying Shopify. If you cannot modify the ERP, a polling job that reads “rows changed since timestamp X” is a reasonable substitute.

6. Reconciliation

Scheduled jobs compare the two systems and repair differences:

  • Orders: list Shopify orders updated in the last window and confirm each exists in the ERP.
  • Inventory: compare quantities per SKU and location; correct or alert.
  • Catalog: confirm every sellable SKU in Shopify maps to an ERP item.

For large datasets, Shopify’s bulk operations let you run a query asynchronously and download the results as a JSONL file, which is far cheaper in rate-limit terms than paging through millions of records. Reconciliation is Shopify’s own recommendation for redundancy, and it is the component teams most often skip.

Cross-cutting concerns

Rate limiting

Treat the Shopify API as a shared, metered resource. Centralize all Shopify calls in one client that:

  • reads the throttleStatus returned in each GraphQL response’s extensions.cost,
  • waits when available capacity is low instead of waiting for a throttle error,
  • retries THROTTLED errors with backoff,
  • requests only the fields it needs (query cost depends on fields selected).

Configuration and mappings

Keep mappings — location IDs to warehouses, payment gateways to ERP payment methods, shipping methods to carriers, tax codes — in configuration tables, not code constants. These change more often than you expect.

Observability

Log every event with a correlation ID that follows it from webhook to ERP document. Track queue depth, oldest pending event age, failure rate per workflow, and end-to-end latency. See designing an integration monitoring system.

Security

  • Verify every webhook signature with a constant-time comparison.
  • Keep access tokens in a secrets manager; rotate them when staff or vendors change.
  • Expose the ERP API only to the integration service, ideally over a private network or with mutual TLS.
  • Redact personal data (addresses, emails, phone numbers) from logs, or set short retention.

Choosing where it runs

Option Good fit Watch out for
Small container service + PostgreSQL Most custom-ERP projects You operate it yourself
Serverless receiver + managed queue + serverless workers Bursty traffic, teams already on a cloud provider Execution time limits, cold starts, connection pooling to the ERP
Shopify → EventBridge or Pub/Sub Teams already on AWS or Google Cloud Still need workers and reconciliation
iPaaS / middleware product Standard ERPs with existing connectors, limited engineering capacity Licensing cost, limits on custom logic

None of these is universally better. The direct vs. middleware comparison goes deeper on the trade-offs.

Simplifications that are usually safe

You do not need every component on day one:

  • One process can host the receiver and the workers if traffic is modest and you accept that a deployment briefly pauses receiving (Shopify will retry).
  • A database table is a perfectly good queue below tens of thousands of events per hour.
  • Reconciliation can start as a nightly report that a person reviews, before it ever writes corrections.

Simplifications that usually hurt

  • Skipping the inbox and processing inside the webhook request.
  • Skipping idempotency because “webhooks are mostly delivered once.”
  • Sharing one generic worker for all workflows, so a poison message in refunds blocks orders.
  • No dead-letter handling, so failed jobs retry forever or disappear.

Testing the architecture

  • Chaos tests: stop the ERP mid-run and confirm events accumulate and drain afterward with no duplicates.
  • Ordering tests: deliver orders/updated before orders/create and confirm the final ERP state is correct.
  • Replay tests: reprocess a day of stored events against a staging ERP and compare results.
  • Rate-limit tests: run a bulk catalog update and confirm the client slows down rather than failing.

Summary

Decouple receiving from processing, store every event durably, fetch current state before writing, make writes idempotent, serialize per resource, publish ERP changes through an outbox, and reconcile on a schedule. Each component answers a specific, documented failure mode. Start with the simplest implementation of each and scale only the parts your measurements say are under pressure.

Frequently asked questions

Do I need Kafka or a message broker for a Shopify ERP integration?

Usually not at the start. A relational database table used as a queue handles thousands of events per hour comfortably. Move to a managed queue when you need higher throughput, multiple independent consumers, or you are already running one.

Should the integration run as serverless functions or a long-running service?

Both work. Serverless suits the webhook receiver because traffic is bursty. Workers that call a slow ERP for many seconds, or need long-lived database connections, are often simpler as a small container or VM service.

Where should mapping logic live — in the ERP or in the integration layer?

In the integration layer, as code you can version and test. Keeping Shopify-specific concepts out of the ERP makes it easier to add another sales channel later.

How do I handle multiple Shopify stores?

Treat each store as a tenant. Store credentials, API versions, location mappings, and cursor positions per shop, and include the shop domain in every event and log line.

Sources and further reading

Platform-specific statements on this page were checked against these sources on . Vendor APIs change; confirm details in the current documentation before you build.

  1. [1] Webhook best practices (ordering, duplicates, reconciliation) — Shopify
  2. [2] Troubleshooting webhooks — Shopify
  3. [3] Shopify API rate limits — Shopify
  4. [4] Bulk operations (queries) — Shopify
  5. [5] Transactional outbox pattern — microservices.io (Chris Richardson)