Skip to content
SyncMyShop

WooCommerce and your ERP

WooCommerce ERP integration

WooCommerce publishes no API rate limit: the limit is your web host, and every API call starts WordPress and its plugins. Orders carry plugin data under keys nobody documented, and stock is one whole number per product. This page gives the numbers for WooCommerce and six ERPs. We build the integration to your ERP and your plugins, then host and run it.

What moves between WooCommerce and an ERP

WooCommerce takes the order and the payment. The ERP holds items, prices, stock, customers on account and the books. Each kind of data has one owner, and the integration writes in one direction per field.

Data System of record Direction WooCommerce side
Items, SKUs, variations ERP ERP to WooCommerce /products, /products/<id>/variations
Descriptions, images, categories WooCommerce stays in WooCommerce not touched by the ERP
Regular and sale price ERP ERP to WooCommerce regular_price, sale_price
Stock ERP or WMS ERP to WooCommerce stock_quantity, one integer
Orders WooCommerce until import WooCommerce to ERP /orders, webhook plus poll
Customers ERP for accounts, WooCommerce for web buyers both ways billing, shipping, customer_id
Order status and tracking ERP or WMS ERP to WooCommerce status, order notes or a tracking extension
Refunds where the refund starts both ways /orders/<id>/refunds

Three things in this table are not in WooCommerce core and come from plugins when a store has them: a price per customer, a company account and a tracking number. Where they live differs per store, so the integration starts with an inventory of the active plugins.

What WooCommerce's API allows

No published limit, a real one

The REST API at /wp-json/wc/v3 documents no rate limit. WooCommerce's built-in rate limiting covers only POST requests on the Store API, the API for the storefront, and is off by default. The working limit is the host: every API call boots WordPress, loads every active plugin and holds one PHP worker until it returns. A shop on a small plan with four workers that receives a burst of API calls has fewer workers for its checkout.

Setting Value
Page size 10 by default, per_page up to 100
Batch 100 objects per request, HTTP 413 above
Variation batch per parent product: /products/<id>/variations/batch
Incremental read modified_after, since WooCommerce 5.8
Authentication consumer key and secret of one WordPress user
Stock stock_quantity, integer; manage_stock can be parent

Catalog, worked through

A store with 800 simple products and 400 variable products with 6,000 variations. The simple products are 8 batch requests of 100. The variations cannot be batched across products: they are 400 requests, one per parent, of 15 variations on average. At an assumed 2 seconds per batch call on a mid-size host, a full update is 816 seconds, a little under 14 minutes of continuous load. A normal run sends only what changed: 300 changed variations spread over 120 products is 120 requests.

Webhooks

WooCommerce delivers webhooks in the background through WP-Cron, and WP-Cron runs only when someone loads a page. On a quiet night an order webhook waits for the next visitor. Any answer other than 2xx, 301 or 302 counts as a failure; the documentation describes no retry, and after 5 consecutive failures the webhook status becomes disabled. So the webhook starts the import and a poll picks up what it missed: GET /wc/v3/orders?modified_after=...&per_page=100 every few minutes picks up whatever the webhook did not deliver. Background in webhooks are not a guarantee.

What WooCommerce and an ERP store differently

A WooCommerce order mapped to a Business Central sales order The WooCommerce order date in the store's time zone becomes orderDate; the GMT date is one day later and is not used. A guest order with customer_id 0 is matched to a customer number by billing email. The PO number from plugin meta data becomes externalDocumentNumber, the coupon discount becomes discountAmount. Business Central's pricesIncludeTax is read-only and comes from the customer, so it has to match the store setting. WooCommerce order 100482 Business Central salesOrder date_created 2026-09-30T20:30:00 store time zone, New York date_created_gmt 2026-10-01T00:30:00 not used: next day in UTC customer_id 0 guest order billing.email purchasing@example.com meta_data._po_number PO-7781 written by a B2B plugin coupon_lines[0].discount 3.37 prices_include_tax false orderDate 2026-09-30 customerNumber C00412 externalDocumentNumber PO-7781 discountAmount 3.37 pricesIncludeTax false read-only, set by the customer date part match on email must agree
One WooCommerce order mapped to Business Central. The GMT date is a day later, the PO number sits in plugin meta data, and the tax setting is fixed on the ERP side.

Dates

date_created is in the site's time zone and carries no offset. date_created_gmt is the same moment in UTC. A store in New York that takes an order at 8:30 pm on September 30 has a GMT date of October 1, 00:30. An ERP fed the GMT date books a September sale in October. The integration takes the date in the time zone the books are kept in. More in dates and time zones.

Orders and statuses

WooCommerce statuses are pending, processing, on-hold, completed, cancelled, refunded, failed and trash. Stock goes down at processing and on-hold; a pending order is waiting for payment and has not taken stock. The integration creates the ERP sales order at processing, or at on-hold for bank transfer and check payments, and writes completed back when the ERP ships.

A guest order has customer_id 0. The ERP needs a customer, so the integration matches on billing email, creates a customer when there is no match, and uses billing.company to choose between a company and a person. Without a fixed matching rule the same buyer ends up as three ERP customers; see duplicate customers and orders.

Plugin data

Discounts arrive in coupon_lines, surcharges in fee_lines, shipping in shipping_lines. Everything plugins add arrives in meta_data, under keys each plugin author chose: a PO number, a delivery date, a VAT number, a subscription ID.

"meta_data": [
  { "key": "_po_number", "value": "PO-7781" },
  { "key": "_delivery_date", "value": "10/14/2026" },
  { "key": "_billing_vat", "value": "" }
]

Each key that matters is mapped to an ERP field, with its format: 10/14/2026 is a US date string, not a date. When a plugin update renames a key, the field arrives empty; the integration reports an order where a required key is missing instead of writing it without.

Stock

One stock_quantity per product or variation, an integer, no location. An ERP that keeps stock per warehouse, or in decimals for goods sold by the foot or the pound, is summed and rounded down before it is written. A variation with manage_stock set to parent has no stock of its own and sells from the parent's quantity, while the ERP holds stock per SKU. Those variations are found before go-live and either switched to their own stock or written as one sum. See multi-location inventory.

Which ERP

ERP API Limit Over the limit
NetSuite REST, SuiteQL, RESTlets 5, 15 or 20 concurrent requests per account 429 CONCURRENCY_LIMIT_EXCEEDED
Business Central API v2.0, OData v4 6,000 requests per 5 minutes and 5 concurrent, per user 429
SAP Business One Service Layer, OData none published; session of 30 minutes, 20 rows per page 401 on an expired session
Odoo JSON-2 from version 19 external API on the Custom plan only not published
ERPNext Frappe REST API off unless the site sets rate_limit 429
QuickBooks Online REST v3 500 requests per minute per company 429, wait 60 seconds

NetSuite. Concurrency is shared by every integration on the account, and a sales order created and saved in a script costs 30 governance units. Worked out in WooCommerce NetSuite integration.

Business Central. OAuth 2.0 only since October 2022; 429 above 6,000 requests in five minutes per user; an operation longer than 8 minutes is stopped. The standard item API returns one inventory number, the same shape as WooCommerce's single quantity. Per-location stock needs a custom API page. More on the Business Central page.

SAP Business One. The Service Layer logs in with POST /Login and returns a B1SESSION cookie that expires after 30 idle minutes, with 20 rows per page unless odata.maxpagesize is set. SAP publishes no request limit, so the server sizing decides. Webhooks exist since 10.0 FP 2602 and are off by default. More on the SAP Business One page.

Odoo. JSON-2 at /json/2/<model>/<method> since Odoo 19; XML-RPC and JSON-RPC are removed in Odoo 22. An API key of a normal user expires after at most 90 days. More on the Odoo page.

ERPNext. GET /api/resource/Sales%20Order returns 20 records by default and only their names; fields and limit_page_length change that. Authentication is a header Authorization: token api_key:api_secret for a dedicated user. Frappe's rate limiter is off unless the site config sets rate_limit, and when it is on it counts seconds of request time per window, not requests. See Shopify ERPNext integration for the ERPNext data model.

QuickBooks Online. 500 requests per minute per company and 10 per second per company and app. An item holds one QtyOnHand, inventory needs the Plus or Advanced plan, and DisplayName must be unique across customers, vendors and employees. More on the QuickBooks page.

Where standard connectors stop

A standard connector maps the documented order schema. In a WooCommerce store a large part of the order lives outside it, and it differs per store.

  • meta_data. PO numbers, delivery dates, VAT numbers and subscriptions sit under plugin keys. A fixed mapping does not know them, and a plugin update can rename them.
  • Prices per customer. Core has none. The plugin that provides them decides where they are stored, so a connector that writes regular_price alone cannot carry an ERP price level.
  • Refunds that pay out. api_refund is true by default. Mirroring an ERP credit memo into a WooCommerce refund without setting it to false triggers a second refund at the payment gateway.
  • Polling the whole catalog. On a host without an API limit, a connector that reads every product every 15 minutes is a constant load on the PHP workers.

A built integration starts from your plugins and the keys they write. The comparison on technical grounds is in custom integration vs iPaaS.

When it goes wrong

What fails What the integration does What you see
Webhook delivery fails next poll with modified_after picks up the order a log line: found by poll
Webhook set to disabled sets it back to active, keeps polling an alert with the time
API key returns 401 stops writing, keeps the queue an alert: key or user removed
Every call returns 404 stops, queue intact an alert: permalinks set to Plain
Host answers 502 or 504 under load longer pause, smaller batches queue length on the dashboard
Required plugin key missing on an order holds that order the order and the key
ERP rejects or throttles the order retries with a growing pause nothing, unless it lasts

A WordPress update, a plugin update or a host migration is the usual moment for one of these rows. A record that still fails after its retries is kept with its original payload and sent again once the cause is fixed. Before go-live the integration runs as a dry run that writes nothing and shows the ERP orders and customers it would create; after that every run leaves a diff per record. This is how we build every e-commerce ERP integration. The WooCommerce side is on the WooCommerce page; sibling pages are Shopify ERP integration and BigCommerce ERP integration. Tell us which plugins and which ERP you run.

Frequently asked questions

Do we need a plugin in WordPress for the ERP integration?

No. The integration runs outside WordPress and uses the WooCommerce REST API with a consumer key and secret. WordPress needs HTTPS, permalinks set to anything other than Plain, and a dedicated user that owns the key. A key stops working when its user is deleted, which is the usual cause of an integration that stops overnight after a staff change.

How many API calls can WooCommerce handle?

WooCommerce documents no limit for its REST API, so the answer is your host. Each call starts WordPress and loads every active plugin, and it uses one PHP worker while it runs. The integration sends batches of up to 100 objects, only for records that changed, and spaces its calls so checkout keeps its workers.

What happens to orders when the integration endpoint is down?

WooCommerce does not resend a failed webhook, and after 5 consecutive failures it disables the webhook. The integration therefore also polls orders with modified_after and checks that its webhooks are still active. An order a webhook missed arrives with the next poll.

Can the ERP show stock per warehouse in WooCommerce?

WooCommerce core has one stock_quantity per product or variation, an integer, and no location field. The integration adds up the ERP warehouses that may ship web orders, subtracts any buffer you set, rounds decimal quantities down, and writes one number.

Which ERP works best with WooCommerce?

Any ERP whose API exposes the fields you need. The differences are in their limits: NetSuite counts concurrent requests per account, Business Central 6,000 requests per 5 minutes per user, QuickBooks Online 500 per minute per company. SAP Business One and self-hosted ERPNext publish no rate limit, so the server they run on decides.

Our B2B prices come from a plugin. Can the ERP feed it?

Yes, once we know where the plugin stores them. WooCommerce core has no price per customer; B2B plugins keep prices and customer groups in their own meta keys or tables, and some expose them through their own REST endpoints. We read the plugin's storage before go-live and write ERP price levels there.

Tell us what you need

Tell us which systems you use and what needs to work better. We reply personally within one working day. We help you define the work, then agree the price and timing before you commit.

  • You know the price and the timeline before you commit to anything
  • We work out the connection that takes the work off your hands
  • We handle the build, hosting, monitoring and maintenance
  • Your team has less information to retype and fewer discrepancies to chase

Suppliers, products, orders per day, a rough estimate is fine.

Or email directly: carsten@syncmyshop.com