Erpy for Exact Online
Erpy for Exact Online connects Craft Commerce to Exact Online through its REST API. It works with every country installation Exact runs: the Netherlands, Belgium, Germany, France, Spain, the United Kingdom and the United States. The add-on is free; it needs Erpy, which is the paid part and owns the sync engine, the identity map, mapping, the queue and the log.
Install
composer require justinholtweb/craft-erpy-exactonline
php craft plugin/install erpy-exactonline
Then Erpy → Connections → New connection and pick Exact Online. The add-on has no settings screen of its own; everything lives on the connection.
What it syncs
| Entity | Direction | Delta sync | Page size |
|---|---|---|---|
| Customers | ERP → Commerce | Yes, on Modified | 60 |
| Products | ERP → Commerce | Yes, on Modified | 60 |
| Prices | ERP → Commerce | Yes, on Modified | 60 |
| Inventory | ERP → Commerce | No | 60 |
| Orders | Commerce → ERP | ||
| Order status | ERP → Commerce | Yes | 60 |
| Shipments | ERP → Commerce | Yes | 60 |
| Invoices | ERP → Commerce | Yes | 60 |
| Credit | ERP → Commerce | No | 60 |
The connector declares multi-company support: one connection per division. The page size of 60
is Exact's own, and paging follows the __next link Exact returns.
Connecting
Exact uses the OAuth 2.0 authorization code flow: somebody approves access once in a browser, and Erpy keeps it alive from then on.
- Pick the region. Exact runs a separate installation per country, and they do not share
data. Choose the one your login belongs to (
start.exactonline.nl,.be,.de,.fr,.es,.co.ukor.com). - Register an app in the Exact App Centre. Copy the Redirect URI shown on the connection form into the app's redirect URI. Exact compares it character for character.
- Fill in the Client ID and Client secret from that app, and save the connection.
- Approve access once. Erpy sends you to Exact's consent screen and stores the tokens it gets back on the connection. Until that has happened, Test connection reports "Not connected yet".
| Field | What to put there |
|---|---|
| Region | The country installation your login belongs to. |
| Redirect URI | Read-only. Copy it into your Exact app. |
| Client ID / Client secret | From your App Centre registration. |
| Division | The numeric division code. |
| Warehouse code | Stock is read from this warehouse. Leave blank for the company total. |
| Base price list ID | Currently has no effect: every sales item price is synced. |
Set the division explicitly before going live. Left blank, the connector uses the login's current division. That is whichever division the user last had open in Exact. Test connection shows both the login's current division and the one in use, so you can check.
Things to know about Exact Online
Refresh tokens rotate. Every refresh invalidates the previous refresh token and issues a new one. Keep the old one and the connection dies the second time it is used. Erpy writes the rotated token back to the connection. It also means two integrations sharing one app registration will keep logging each other out. If Test connection reports a 401 saying the refresh token is no longer valid, that is almost always the cause. Give Erpy its own app and approve access again.
A wrong division is a silent failure. Everything in Exact is scoped to a division, and asking the wrong one returns an empty, entirely successful response. That is worse than an error, which is why the setting's instructions say so twice.
The rate limit is real, and daily. Exact allows roughly one call a second and a hard cap per
day, and answers 429 beyond either. The transport stays under the per-minute limit and honours
Exact's Retry-After for the rest. Schedule large syncs accordingly: a full catalogue pull spends
daily allowance a delta sync does not.
Stock is economic stock. Exact publishes on-hand less what is already committed to sales orders, which is the figure a storefront should quote.
Order status is numeric. 12 is open, 20 partially delivered, 21 complete, 45 cancelled.
Your order number is YourRef. The Commerce order number goes out in the sales order's
YourRef field (up to 50 characters). A retry checks it before creating a second sales order, and
order status comes back matched on it.
Every order needs an account and every line needs an item. The connector finds the Exact account by the linked customer, then by customer code, then by email address. An order that matches none is refused. So is a line whose SKU is not an Exact item code. Set a Guest customer code on the order mapping if guests check out.
Credit arrives without a balance. Credit reads the sales credit line and the blocked flag from the account, but the balance comes through as zero.
Dates look strange in the raw payload. Exact's OData v2 dates arrive as
/Date(1596499200000)/: milliseconds since the epoch, wrapped. The connector converts them, but
a mapping rule that reads a date from raw. gets the wrapped string.
Correcting a field
The customer sync reads the credit limit from the account's CreditLinePurchase field, which is
the supplier-side limit. If you want the sales credit line on the customer record, correct it on
the Customers mapping:
raw.CreditLineSales → creditLimit transform: number
The same pattern fixes a SKU. The connector reads the item's Code; if you sell under a different
item field, map raw.<thatField> → sku on the Products mapping.
See Field mapping for the rule syntax and the full transform list, Syncing for runs and watermarks, and Troubleshooting when something does not arrive.