Troubleshooting
Start here
- Is it Erpy or the connector? Make a Mock ERP connection and run the same flow. The Mock makes no network calls and invents deterministic data. If it works there, the problem is the connector, the credentials or the tenant — not the engine.
- Test connection. It calls the connector's health check and names the credential to look at.
- Erpy → Log. Every request and response, with secrets redacted. Filter to errors.
- Erpy → Problems. A refused document is kept whole, with the reason.
The sync says it succeeded but nothing changed
Almost always the delta watermark. The ERP was asked for "changed since X" and answered honestly that nothing had.
php craft erpy/sync/status # what the watermark currently is
php craft erpy/sync/reset acme product # forget it; next run is a full one
php craft erpy/sync/run acme product --full
If a full run also changes nothing, check the mapping preview — a rule producing null writes
nothing, and a rule pointing at a field that no longer exists silently does the same.
Products sync but stock does not
Check that the variants track inventory. A variant that does not track inventory is skipped by the stock sync. Erpy sets the flag on variants it creates; variants created some other way may not have it.
Orders are not reaching the ERP
php craft erpy/orders/missing acme
Then, in order:
- Is
pushOrdersOnCompleteon? - Is the queue running? Push is always queued, so a stalled queue means no orders move. This is the most common cause by a wide margin.
- Is
pushOrderDelaySecondsset to something longer than you remember? - Is the connection enabled, and does its connector declare a push direction for orders?
- Anything on Problems?
php craft erpy/orders/push acme 1000123 --dry-run
shows the exact payload without sending it.
A run is stuck on "running"
Its worker was killed — a deploy, an OOM, a restarted container. Erpy marks a run stale after
staleRunMinutes (default 60) and lets the next one start. The watermark did not move, so nothing
was lost.
The same order appeared twice in the ERP
It did not come from Erpy. The identity map has a unique index on the pairing, and a second insert is refused by the database rather than by a check that could be raced. Look for a second integration, a manual entry, or an ERP-side workflow that copies orders.
Prices on the storefront are Commerce's, not the ERP's
Contract pricing fails open by design: a bad lookup falls back to Commerce's own price rather than taking the storefront down. So a wrong price is a silent fallback, not an error page.
- Is
applyContractPricingon? - Has the price sync run for this connection?
- Does
craft.erpy.price(variant, 1)returnnullfor this customer? That means the ERP has no contract line for the pairing, which is a data question rather than a plugin one.
An error message contains something that looks like my API key
It should not. The transport redacts secrets out of non-2xx bodies before a connector or a health
screen ever sees them. Success bodies are left alone, because connectors have to parse them — so a
credential echoed inside a 200 response can still reach the log if logBodies is on. Turn it
off on a connection whose ERP does that, and tell me which ERP so the connector can strip it.
Everything is slow
logBodieson a 40,000-SKU sync writes a lot of rows. TrylogErrorsOnly.recordSkippedItemson a delta sync records the 39,900 rows that did not change. Leave it off.- Run
erpy/sync/duefrom cron instead of the queue. Cron has no request timeout. - Check the per-entity intervals. Inventory wants minutes; a product catalogue does not.
Getting help
justin@justinholt.com. Useful things to include: the connector and ERP version, what
php craft erpy/sync/status prints, and whether the same flow works against the Mock ERP.