Short answer
Build a small integration service between Shopify and your ERP. Shopify webhooks tell the service that something changed; the service queues the event, fetches authoritative data from the GraphQL Admin API, maps it, and writes it to the ERP through an API you control. A scheduled reconciliation job catches anything webhooks missed. Start with one workflow (usually orders), make it idempotent, and add inventory, fulfillment, and refunds one at a time.
- Shopify Webhook + Admin GraphQL API
- Receiver Verify HMAC
- Queue Durable job store
- Worker Fetch
- Custom ERP Internal API
Webhooks trigger work, but the integration service always confirms state through the API before writing to the ERP.
Integrating Shopify with a custom ERP is mostly a reliability problem, not an API problem. Fetching an order from Shopify takes a few lines of code. Making sure every order reaches the ERP exactly once, in the right state, even when the ERP is down for maintenance or a webhook arrives twice, is where the real work lives.
This guide walks through a practical sequence for building that integration. It assumes you control the ERP (or at least its API) and that you are writing the integration code yourself or with a development partner.
This is an end-to-end plan. Each step links to a deeper guide. Code samples are illustrative and deliberately incomplete — they show the shape of a solution, not a production-ready integration.
Step 1: Define the workflows before touching an API
Write down every business process that crosses the Shopify–ERP boundary, and for each one decide which system owns the data. A typical first list looks like this:
| Workflow | Direction | Usual system of record | Trigger |
|---|---|---|---|
| New orders | Shopify → ERP | Shopify until accepted by ERP | Order created or paid |
| Inventory levels | ERP → Shopify | ERP or warehouse system | Stock movement in ERP |
| Products and prices | ERP → Shopify | ERP or PIM | Item change in ERP |
| Fulfillment and tracking | ERP → Shopify | ERP or WMS | Shipment confirmed |
| Refunds and cancellations | Both | Depends on process | Refund or cancel in either system |
| Customers | Shopify → ERP | Usually Shopify for B2C | Order or account creation |
The “system of record” column matters more than anything else on this page. If both systems can change a product’s price and you have not decided which one wins, no amount of good code will keep them consistent.
For each workflow, also note:
- Latency requirement. Does inventory need to update within a minute, or is every 15 minutes acceptable? This decides between event-driven and scheduled processing. See real-time vs. scheduled synchronization.
- Volume. Peak orders per hour, number of SKUs, number of locations. These drive rate-limit planning.
- Failure tolerance. What happens if an order reaches the ERP an hour late? What if it reaches it twice?
Step 2: Check what your ERP can actually accept
Custom ERPs vary enormously. Before designing anything, find out:
- Is there an API? REST, SOAP, a database you can write to, a file import folder? A proper API is strongly preferred over direct database writes, because it keeps the ERP’s business rules in force.
- Can it store an external reference? You need a field on the ERP order to hold the Shopify order ID. This is the foundation of duplicate prevention.
- Does it reject duplicates? A unique constraint on that external reference turns a silent duplicate into a visible error.
- What are its maintenance windows? Nightly batch jobs that lock tables are a common cause of failed integrations.
- How does it identify products? If the ERP uses internal article numbers and Shopify uses SKUs, you need a mapping. See mapping products to ERP articles and SKUs.
If the ERP has no usable API, building a thin one in front of it is often the best investment of the whole project.
Step 3: Choose an integration architecture
For most custom-ERP projects, the right starting point is a small dedicated integration service:
- A webhook receiver that verifies and stores incoming events, then responds immediately.
- A durable queue (a database table works fine at moderate volume; a managed queue service at higher volume).
- One or more workers that process jobs: fetch fresh data from Shopify, map it, and call the ERP.
- A reconciliation job that periodically compares both systems and repairs gaps.
- Logging and alerting so someone notices when jobs fail.
This is the pattern shown in the diagram above. It keeps the ERP free of Shopify-specific logic and lets you retry, replay, and inspect every message. The architecture guide covers the components and alternatives in more depth, including when an iPaaS or middleware product is the better choice.
Step 4: Set up API access in Shopify
Your integration authenticates to Shopify as an app. For a single store you will typically create a custom app for that store; for multiple stores or a commercial product you build a distributed app with OAuth. Follow Shopify’s current app documentation for the setup flow, because the tooling for creating apps changes over time.
Key decisions at this step:
- Request the minimum access scopes. An order export needs order read access; inventory updates need inventory write access. Avoid broad scopes “just in case.”
- Pin an API version. Shopify releases a new API version every quarter, and each stable version is supported for at least 12 months. Put the version in configuration, not scattered through the code, and schedule an upgrade review every quarter.
- Use the GraphQL Admin API. Shopify labels the REST Admin API as legacy, and new public apps have been required to use GraphQL exclusively since April 1, 2025.
- Store credentials as secrets. Access tokens and the app’s client secret (used to verify webhooks) belong in a secret manager or environment variables, never in source control.
Step 5: Subscribe to webhooks and verify them
Webhooks are how Shopify tells your service that something happened. For an order-first integration, the typical topics are orders/create or orders/paid, orders/updated, orders/cancelled, and refunds/create. You can deliver webhooks over HTTPS or to Amazon EventBridge or Google Cloud Pub/Sub.
Every HTTPS delivery includes an X-Shopify-Hmac-Sha256 header. Your receiver must verify it before trusting the payload:
import crypto from 'node:crypto';
// rawBody must be the exact bytes Shopify sent, before any JSON parsing.
export function isValidShopifyWebhook(rawBody: Buffer, hmacHeader: string | undefined, secret: string): boolean {
if (!hmacHeader) return false;
const digest = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
const a = Buffer.from(digest);
const b = Buffer.from(hmacHeader);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Three documented behaviors shape how the receiver must work:
- Shopify expects a response within five seconds. Slower responses count as failures. Do not call the ERP inside the webhook request.
- Failed deliveries are retried up to eight times over four hours, and if failures persist the subscription is removed. A broken receiver can silently stop your integration.
- Ordering is not guaranteed, and the same event can arrive more than once. Use the
X-Shopify-Webhook-Idheader to detect duplicates and timestamps (X-Shopify-Triggered-Ator the resource’supdated_at) to discard stale updates.
The receiver therefore does three things only: verify, store, acknowledge.
app.post('/webhooks/shopify', express.raw({ type: 'application/json' }), async (req, res) => {
if (!isValidShopifyWebhook(req.body, req.get('X-Shopify-Hmac-Sha256'), process.env.SHOPIFY_CLIENT_SECRET!)) {
return res.sendStatus(401);
}
// Insert is idempotent: a unique index on webhook_id ignores repeat deliveries.
await db.query(
`INSERT INTO inbound_events (webhook_id, topic, shop, triggered_at, payload)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (webhook_id) DO NOTHING`,
[req.get('X-Shopify-Webhook-Id'), req.get('X-Shopify-Topic'), req.get('X-Shopify-Shop-Domain'),
req.get('X-Shopify-Triggered-At'), req.body.toString('utf8')],
);
res.sendStatus(200);
});
The webhook reliability guide covers this pattern in detail, including what to do when the subscription is removed.
Step 6: Process events in a worker
A worker picks up stored events and does the real work. A robust worker for orders:
- Loads the event and extracts the order ID.
- Fetches the current order from the GraphQL Admin API instead of trusting the webhook payload. The payload is a snapshot; by the time you process it, the order may have been edited, paid, or cancelled.
- Checks whether the ERP already has it using the Shopify order ID as an external reference.
- Maps the order into the ERP’s format: customer, addresses, lines, taxes, discounts, shipping, payment status.
- Writes to the ERP and records the ERP’s document number against the Shopify ID.
- Marks the event as processed, or schedules a retry with backoff if the ERP call fails.
A minimal order fetch looks like this:
query OrderForErp($id: ID!) {
order(id: $id) {
id
name
createdAt
updatedAt
cancelledAt
displayFinancialStatus
currencyCode
totalPriceSet { shopMoney { amount currencyCode } }
lineItems(first: 100) {
nodes {
id
sku
quantity
title
originalUnitPriceSet { shopMoney { amount } }
}
}
}
}
Check the GraphQL response for both top-level errors and per-field problems. The GraphQL Admin API can return HTTP 200 even when a request was throttled or failed, so status codes alone are not enough. The order synchronization guide has a complete field-mapping checklist.
Step 7: Build in idempotency and retries from day one
Every step above can fail halfway. The ERP might create the order and then time out before responding. Your worker will retry, and without protection you get a duplicate order.
The defenses, in order of importance:
- A unique external reference on the ERP side (the Shopify order GID or numeric ID). The ERP refuses a second order with the same reference.
- A “check, then create” step in the worker, so a retry after a timeout finds the existing order instead of creating another.
- Retries with exponential backoff and jitter, a maximum attempt count, and a place for jobs that exhaust their retries (a dead-letter queue).
- Respect for Shopify’s rate limits. The GraphQL Admin API uses a cost-based leaky bucket. Read the throttle information returned with each response and slow down before you are throttled.
These topics each have their own guide: preventing duplicate orders, retries and idempotency, and handling failed sync jobs.
Step 8: Add the reverse flows
Once orders flow reliably, add workflows that write back to Shopify:
- Inventory from the ERP to Shopify. This is usually the second most valuable workflow and the easiest to get subtly wrong. See inventory synchronization.
- Fulfillment and tracking from the ERP or warehouse to Shopify, so customers get shipping notifications. See shipment tracking sync.
- Refunds and cancellations, which touch money and stock and need careful rules. See refunds and cancellations.
Add them one at a time, each with its own monitoring, so that a problem in one flow does not hide behind noise from another.
Step 9: Reconcile on a schedule
Shopify explicitly advises apps not to rely solely on webhooks and to run reconciliation jobs that periodically fetch data. In practice:
- Every 15–60 minutes, query Shopify for orders updated since the last successful run (the
ordersquery accepts anupdated_atfilter) and enqueue any the ERP does not yet have. - Nightly, compare inventory levels for every SKU and report or correct drift.
- Weekly, compare order totals by day between systems to catch mapping errors.
Reconciliation is what turns “usually works” into “provably consistent.” It is covered in inventory reconciliation and in the webhooks vs. polling guide.
Step 10: Test like production will break it
Before go-live:
- Use a development store with realistic products, variants, discounts, tax settings, and shipping profiles.
- Test the unhappy paths deliberately: ERP offline, ERP slow, duplicate webhook, webhook out of order, order edited after creation, partial refund, cancelled order, SKU missing in the ERP.
- Replay real payloads. Store sample webhook bodies and run them through the worker in automated tests.
- Load test the worker at a multiple of your expected peak (for example, a holiday sale), and confirm you stay within Shopify’s rate limits.
- Run in parallel with the old process for a period, comparing results daily, before switching off manual entry.
Step 11: Operate it
An integration is a running system, not a finished project. Plan for:
- Dashboards showing queue depth, processing latency, failure counts, and the age of the oldest unprocessed event.
- Alerts that fire when failures exceed a threshold or when no orders have arrived for longer than normal.
- A replay tool so support staff can reprocess a failed order after fixing its data.
- A quarterly API version review to stay ahead of Shopify’s version retirement.
The monitoring guide explains what to measure and when to alert.
Common mistakes
- Calling the ERP inside the webhook request. One slow ERP response and Shopify starts retrying, multiplying the load.
- Trusting webhook payloads as current state. Always fetch the latest version before writing.
- No external reference in the ERP. Without it, duplicate prevention depends on luck.
- Two-way sync without a clear owner. If both systems can edit the same field, define a winner per field.
- No reconciliation. Webhook subscriptions can be removed after repeated failures; without reconciliation nobody notices until a customer complains.
- Logging only errors. You also need a record of successful processing to answer “did order #1234 reach the ERP?”
Summary
Start with a clear list of workflows and owners, check what the ERP can accept, and build a small service that verifies webhooks, queues them, fetches fresh data from the GraphQL Admin API, and writes to the ERP idempotently. Add reconciliation and monitoring before you add more workflows. Done in this order, a custom ERP integration is a series of manageable steps rather than one risky launch.
Frequently asked questions
Do I need a Shopify Plus plan to integrate with an ERP?
No. The Admin API and webhooks are available to apps on standard plans. Plus adds features that some integrations use, such as higher API limits and B2B functionality, so check whether your specific workflows depend on a Plus-only feature.
Should I use the REST Admin API or the GraphQL Admin API?
Use GraphQL. Shopify describes the REST Admin API as legacy and requires new public apps to use GraphQL exclusively since April 1, 2025. Newer features may only be available in GraphQL.
Can the ERP call Shopify directly without a middle layer?
It can, but you then need to build webhook handling, retries, rate-limit handling, and logging inside the ERP. Most teams find a separate integration service easier to operate, test, and change.
How long does a basic Shopify-to-ERP integration take?
It depends on the number of workflows, the state of the ERP's API, and data quality. A single, well-scoped order export can be a few weeks of engineering; a full order, inventory, catalog, fulfillment, and refund integration is usually a multi-month project. Use the cost estimator for a structured starting point.
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] Shopify GraphQL Admin API reference — Shopify
- [2] Shopify REST Admin API (legacy notice) — Shopify
- [3] Webhook best practices — Shopify
- [4] Troubleshooting webhooks (timeouts and retries) — Shopify
- [5] API versioning — Shopify
- [6] Shopify API rate limits — Shopify
Related reading
- Guides 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.
- Guides How to Synchronize Shopify Orders with an ERP How to export Shopify orders to an ERP reliably — choosing the trigger, mapping lines, taxes, discounts and payments, handling edits, and avoiding duplicates.
- Guides How to Handle Shopify Webhooks Reliably Build a Shopify webhook receiver that never loses events — HMAC verification, the five-second rule, deduplication, ordering, retries, and subscription removal.
- Guides How to Implement Retries and Idempotency A practical implementation of retries with exponential backoff and jitter, plus idempotency keys, so ecommerce-ERP integrations can retry safely without side effects.
- Platforms Shopify ERP Integration How Shopify connects to an ERP — the GraphQL Admin API, webhooks, inventory states, fulfillment orders, rate limits — plus the sync patterns and pitfalls that matter.
- Tools Integration Architecture Planner Get a general architecture recommendation based on your systems and workflows.