Examples
Six recurring patterns of integration. Each one names the method it uses, the calls in order, and the most common error.
1. A carrier module
Section titled “1. A carrier module”Context. A shipping provider with an API. The integration needs their labels, their limits and their end-of-day close, without Stockly holding any provider-specific code.
Integration method. Extension points, in-process.
Implementation steps.
- Implement
CarrierProfileInterfaceand tag the service. The carrier then appears in the carrier select on every packaging rule, with its own limits and its own validations. - Listen to
p2lab.stockly.parcels.commit, or let the merchant’s rule trigger a booking, and call the provider once per parcel. - Report each tracking code back with
p2lab.stockly.parcel.tracking. Stockly writes it to the order’s delivery, which is where the shipping confirmation mail reads it from. - If the provider has an end-of-day close, implement
CarrierManifestGatewayInterfaceand declaremaxParcelsPerCall(). Stockly chunks the hand-over to that number.
Common error. Booking twice. A parcel that already carries a tracking code must never be
re-requested: the shipment exists and the merchant has paid for it. Stockly guards this with
isRequestable(), which has to be read before calling out; a retry loop that ignores it produces two
labels for one shipment.
A second common error. Declaring limits the contract does not have. Limits are contractual as often as technical: the same carrier is 31.5 kg for one merchant and 20 kg for another whose staff may not lift more. Supply the carrier’s defaults and let the merchant’s rule override them.
2. A desktop label tool
Section titled “2. A desktop label tool”Context. The merchant already runs a small program that talks to the carrier and drives the label printer. That program keeps its role, and Stockly is told the outcome.
Integration method. HTTP, polling.
Implementation steps.
-
Poll
/api/search/p2lab-stockly-parcelfor boxes with no tracking code:{ "filter": [{ "type": "equals", "field": "trackingCode", "value": null },{ "type": "equalsAny", "field": "status", "value": ["planned", "packed"] }],"associations": { "order": {}, "lines": {} } } -
Book the label in the external program.
-
Report it:
POST shipping/parcels/{parcelId}/tracking. -
At the end of the day, read
GET shipping/batches/eligibleandPOST shipping/batches/close. The close returns ajobId, not a result: it runs in the background, and the PDF the driver signs is atshipping/batches/{batchId}/list.
Common error. Treating batches/close as synchronous. A day’s hand-over is thousands of
writes and a carrier API that takes thirty shipments at a time; the endpoint returns a job id
immediately and the tool has to poll for the outcome.
Operational note. Filter by station too if the merchant runs more than one packing bench; otherwise
the tool’s end-of-day close signs for boxes still standing at another bench. The field is
floorElementId on the parcel, and shipping/batches/close takes it as a scope.
3. An ERP that owns the weights
Section titled “3. An ERP that owns the weights”Context. Article master data lives in the ERP, and it is better than what is in Shopware.
Integration method. DAL, on a schedule.
Implementation steps.
- Push weights and dimensions into the Shopware product itself:
weight,length,width,height. Stockly reads the native fields; it keeps no second copy. - Push packaging overhead into
p2lab_stockly_product_packaging, keyed byproductId. - If the ERP works in packing classes rather than per article, create one
p2lab_stockly_packaging_profileper class and let the property match do the work: three hundred articles covered by one row that never has to be synchronised again. - After a sync, read
GET packaging/coverageand logsummary.missingDimensions. It is the cheapest possible regression test on the export itself.
Common error. Writing zeros. An ERP that exports “0.000” for an unknown packaging weight
states that the article ships unpackaged, and no rule underneath corrects it. Omit the field, or
send null.
A second common error. Variants. A variant that inherits its weight from its parent stores NULL,
and an importer that corrects this by copying the parent’s value onto every variant turns one row of
master data into a thousand rows nobody will maintain.
4. A 3PL that does its own cartonization
Section titled “4. A 3PL that does its own cartonization”Context. The warehouse belongs to a logistics provider, which decides the boxes and reports them afterwards.
Integration method. HTTP, push.
Implementation steps.
-
Do not call
parcels/plan; this path reports a result rather than asking for one. -
POST shipping/parcels/commitwith the boxes as they really were:{ "orderId": "0191…","source": "acme-3pl","parcels": [{ "sequence": 1,"weightKg": 6.42,"packagingMaterialId": "0191f2…","volumetricWeightKg": 4.10,"note": "outer carton dented, retaped","lines": [{ "lineItemId": "0191…", "quantity": 2 }] }] } -
Report tracking codes as in case 2.
Common error. Re-sending a plan after labels exist. commit replaces the split, but boxes
that already carry a tracking code are frozen and left exactly as they are, so a re-send does not
corrupt booked shipments, but it does not update them either. If the 3PL repacked a booked box, cancel
it and send a new one.
Recommended practice. Send packagingMaterialId even if the catalogue was built only for this. It
is what later lets the merchant ask whether the 3PL is using sensible box sizes, a question with a
direct effect on shipping cost.
5. Monitoring and BI
Section titled “5. Monitoring and BI”Context. Somebody wants to know whether the packing data is decaying, without opening the admin.
Integration method. HTTP, read-only.
What to read, and what it means.
| Call | Alert when |
|---|---|
GET packaging/coverage | summary.hasGlobalProfile is false — the whole feature is inert |
GET packaging/coverage | ready / total falls week over week — new articles arriving unmeasured |
GET packaging/collisions | the list is non-empty — two rules tie and nobody chose |
GET packaging/variance | avgVarianceKg above a threshold — an article’s data is wrong |
Common error. Alerting on the size of gaps. It lists everything unmeasured and stays permanently
long in a shop with a long tail. The number that matters is the top of it: the
articles with high shipments and hasDimensions: false.
6. A workstation kiosk
Section titled “6. A workstation kiosk”Context. A screen at the bench that a packer drives with a scanner, in a front end of the integrator’s own.
Integration method. HTTP.
Implementation steps.
- Resolve the scanned code:
GET /api/_action/p2lab-stockly/packing/scan/resolve?code=…. It tries the order-barcode prefix, then order numbers, then document numbers, and never guesses: an ambiguous code comes back asambiguouswith the candidates. - Ask for a split:
POST shipping/parcels/plan. - Show it, let the packer change it, then
POST shipping/parcels/commitwithfloorElementIdset to the bench. That stamp is the only moment anybody knows which of three benches handled the order. POST shipping/parcels/packedwhen the run finishes, so the merchant’s booking trigger fires.
Common error. Losing the bench. floorElementId is stamped once and never overwritten with
null, precisely so a later save from a screen that does not know the bench cannot erase it; but if
the kiosk never sends it in the first place, the station reports stay empty, and the omission surfaces
only when somebody reads them.
Operational note. Blocked orders come back from the scan resolver with a reason (cancelled, unpaid), not as “not found”. Show the reason, so nobody is sent back to the scanner for an answer that will not change.
A checklist for any integration
Section titled “A checklist for any integration”- Read the four ways in before choosing one; most integrations that caused trouble chose the wrong one first.
- Never write
0in place of “unknown”. - Surface
*Source: "none"to the integration’s own users. It is how bad data gets found. - Treat
batches/closeas a job, not a call. - If the shop has more than one packing bench, carry
floorElementIdeverywhere possible.