Click here to book a no-obligation intro call!

Posted by Markus Wals

Connecting a fulfiller to weclapp: the complete API guide including batch tracking

API Chargen Fulfillment Logistik weclapp

Connecting a fulfiller to weclapp: the complete API guide including batch tracking

We build and run Fulfillment 4 weclapp, have implemented and operate fulfiller and WMS integrations for our customers, and train logistics providers on the weclapp API every week. Even so, we keep seeing the same mistakes. Most recently last week: a fulfiller wrote a merchant's stock to weclapp via stocktaking documents, several times a day. After five days there were 30 stocktaking documents in the system, goods issues were being booked back and forth as inventory differences, and tracking numbers arrived without a shipping status. The go-live was already done. This article is the guide that such an integration would have needed from the start: the complete process, all requests, batch tracking included.

The target picture: weclapp leads, the fulfiller reports

A fulfiller integration is not a full sync between two ERP systems. weclapp remains the leading system for sales orders, deliveries, stock and valuation. The fulfiller picks up deliveries, reports back what physically happened, and corrects stock only as a difference. That makes exactly seven touchpoints, and their order is part of the contract.

Architecture of the fulfiller integration with weclapp: shop, weclapp as the leading system and fulfiller with seven numbered API touchpoints

  1. Fetch deliveries: all deliveries in your own warehouse with the status “delivery note printed”.
  2. Report status back: a custom field on the delivery note as a handshake, so that nothing gets imported twice.
  3. Set picks: storage place, batch and actual quantity per item.
  4. Report shipping: status SHIPPED, shipping carrier and tracking number in a single request.
  5. Fetch the purchase order as advance notice: the merchant's confirmed purchase orders to your warehouse, with delivery date and quantities.
  6. Goods receipt per delivery: against the purchase order, several for partial deliveries, with batch and best-before date. The valuation price comes from the purchase order.
  7. Reconcile stock: book the difference between physical stock and weclapp stock as a stock movement. Never as a stocktake.

Five ground rules before you send the first request

1. Only your own warehouse

A merchant can have several fulfillers, for example one per country. Every query for deliveries and stock therefore filters on the warehouse ID the merchant assigned to you: warehouseId-eq=<your warehouse ID>. Without this filter you ship other people's orders.

“There are different warehouses. It would be really dangerous if you don't put a filter on that.”
Markus Wals, weclapp API Deep Dive for fulfillers

2. Always project, never reload

Every list call gets a properties parameter with exactly the fields you need. Unfiltered lists load the complete entity schema, which costs seconds instead of milliseconds and puts load on the tenant. Referenced data such as article number or carrier name you pull in the same request via includeReferencedEntities using the colon syntax (article:articleNumber), and you never reload it individually per article. You paginate lists with pageSize=1000, page by page, until a page returns fewer than 1000 entries.

3. PUT only with ignoreMissingProperties=true

A PUT without this parameter is a full update: everything you don't send along gets cleared. This also applies to lists in the body. If you send only one item with a delivery, the others are gone.

“If you don't send ignoreMissingProperties, that's a full update. If you happen to hit all the required fields and don't send the rest, you null out everything else.”
Markus Wals, weclapp API Deep Dive for fulfillers

4. Difference instead of absolute value

You never set stock as an absolute value in weclapp. You read the actual stock, form the difference to your physical stock and book only that difference as a stock movement. The stocktake is not meant for this, more on that below.

5. Order and repeatability

Process shipping notifications first, then reconcile. Goods receipts immediately upon receiving the goods, not via the next reconciliation. Every run must be repeatable: an aborted request must not create anything twice on the next pass. You answer 429 with backoff, and 409 (optimistic lock) and timeouts with a read-back instead of blind retries.

Step 1: Fetch deliveries

In weclapp, a delivery is created from the sales order. As soon as the delivery note is printed, the delivery has the status DELIVERY_NOTE_PRINTED. That is the handover point to the fulfiller, which is also how WMS integrations like Pulpo or TM3 do it. Before that, the delivery has not been released.

The request fetches all open deliveries of your warehouse, with items, article reference, carrier and recipient address in a single call. weclapp accepts only -eq on status, not -in.

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/shipment?page=1&pageSize=1000&properties=id%2Cstatus%2CshipmentNumber%2CshipmentItems.quantity%2CshippingCarrierId%2CshipmentItems.articleId%2Carticle%3AarticleNumber%2Carticle%3Aean%2Carticle%3Aid%2Carticle%3Aname%2CshippingCarrier%3Aid%2CshippingCarrier%3Aname%2CpackageReferenceNumber%2CpackageTrackingUrl%2CpackageWeight%2CrecipientAddress%2CrecordEmailAddresses%2CmainSalesOrderId%2CsalesOrder%3Aid%2CsalesOrder%3AorderNumber%2CsalesOrder%3AsalesChannel&includeReferencedEntities=shippingCarrierId%2CshipmentItems.articleId%2CmainSalesOrderId&warehouseId-eq=2315&status-eq=DELIVERY_NOTE_PRINTED' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****'

Two things stand out on the first call. First: the delivery item carries no article number, it only comes via the article reference. Second: shipmentNumber is your reference number for the entire process, all the way back to the shipping label. You read the carrier IDs once via GET /shippingCarrier and map them to your shipping methods.

Which articles come to you at all, you likewise read once, cleanly projected: only storable, active articles, with batch and serial number requirements and the primary supply source for the purchase price.

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/article?pageSize=1000&properties=id%2CarticleNumber%2Cname%2CavailableInSale%2Cactive%2CarticleType%2CprimarySupplySourceId%2CbatchNumberRequired%2CserialNumberRequired%2CarticleSupplySource%3Aid%2CarticleSupplySource%3AarticlePrices%2CarticleSupplySource%3AsupplierId&includeReferencedEntities=primarySupplySourceId&articleType-eq=STORABLE&active-eq=true&availableInSale-eq=true' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****'

Step 2: Report status back, without dashboard access

The merchant wants to see in weclapp where their delivery stands: picked up, in picking, shipped. And you want to fetch only the deliveries you don't know yet on the next run. Both are solved by a custom field of type list on the delivery note. Create it via API, not manually in the interface, otherwise the IDs differ for every merchant and your integration is not reproducible.

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/customAttributeDefinition' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{
  "attributeKey": "fulfillerShipmentStatus",
  "attributeType": "LIST",
  "entities": ["shipment"],
  "label": "Versandstatus",
  "mandatory": false,
  "selectableValues": [
    { "defaultValue": true,  "positionNumber": 1, "value": "Neu" },
    { "defaultValue": false, "positionNumber": 2, "value": "Importiert" },
    { "defaultValue": false, "positionNumber": 3, "value": "Bereit zur Kommissionierung" },
    { "defaultValue": false, "positionNumber": 4, "value": "Kommissionierung" },
    { "defaultValue": false, "positionNumber": 5, "value": "Versendet" }
  ],
  "showInOverview": true
}'

The response returns the ID of the definition and the IDs of the values. After the import you set the value to “Importiert” (imported), again with ignoreMissingProperties=true:

curl --location --request PUT 'https://testhandel.weclapp.com/webapp/api/v2/shipment/id/215289?ignoreMissingProperties=true' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{
    "customAttributes": [{
        "attributeDefinitionId": "7396436",
        "selectedValueId": "7396437"
    }]
}'

From now on, when fetching you additionally filter on the value of the custom field. For a single value the short filter customAttribute<DefinitionID>.id-eq is enough:

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/shipment?page=1&pageSize=1000&properties=id%2Cstatus%2CshipmentNumber%2CshipmentItems.quantity%2CshippingCarrierId%2CshipmentItems.articleId%2Carticle%3AarticleNumber%2Carticle%3Aean%2Carticle%3Aid%2Carticle%3Aname%2CshippingCarrier%3Aid%2CshippingCarrier%3Aname%2CpackageReferenceNumber%2CpackageTrackingUrl%2CpackageWeight%2CrecipientAddress%2CrecordEmailAddresses%2CmainSalesOrderId%2CsalesOrder%3Aid%2CsalesOrder%3AorderNumber%2CsalesOrder%3AsalesChannel%2CcustomAttributes&includeReferencedEntities=shippingCarrierId%2CshipmentItems.articleId%2CmainSalesOrderId&warehouseId-eq=2315&status-eq=DELIVERY_NOTE_PRINTED&customAttribute7396436.id-eq=7396437' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****'

To fetch several values at once, for example “Neu” (new) or “Importiert” (imported), use the expression filter filter= with logical operators:

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/shipment?page=1&pageSize=1000&filter=(customAttribute7396436.id%20%3D%20%277396437%27)%20or%20(customAttribute7396436.id%20%3D%20%277396438%27)&properties=id%2Cstatus%2CshipmentNumber%2CshipmentItems.quantity%2CshippingCarrierId%2CshipmentItems.articleId%2Carticle%3AarticleNumber%2Carticle%3Aean%2Carticle%3Aid%2Carticle%3Aname%2CshippingCarrier%3Aid%2CshippingCarrier%3Aname%2CpackageReferenceNumber%2CpackageTrackingUrl%2CpackageWeight%2CrecipientAddress%2CrecordEmailAddresses%2CmainSalesOrderId%2CsalesOrder%3Aid%2CsalesOrder%3AorderNumber%2CsalesOrder%3AsalesChannel%2CcustomAttributes&includeReferencedEntities=shippingCarrierId%2CshipmentItems.articleId%2CmainSalesOrderId&warehouseId-eq=2315&status-eq=DELIVERY_NOTE_PRINTED' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****'

Step 3: Report picking: picks with storage place and batch

Picks (withdrawals) hang on the delivery item, not on the article. They carry storage place, quantity and, for batch-tracked goods, the batch with best-before date. Via the pick quantity you also represent partial and short deliveries: the reported quantity is the actual delivery quantity. The endpoint for this is POST /shipment/id/{id}/updatePicks. It is documented in the current OpenAPI v2 (required fields picks and updateShipQuantity), but works just the same under /api/v1. Three requests, in this order:

1. Fetch the delivery with items. You need the ID of the delivery and the IDs of the items.

export WC_TOKEN='DEIN_TESTHANDEL_API_TOKEN'
curl -s -H "AuthenticationToken: $WC_TOKEN" \
  "https://testhandel.weclapp.com/webapp/api/v1/shipment?shipmentNumber-eq=LS1550&properties=id,shipmentNumber,warehouseId,shipmentItems.id,shipmentItems.articleId,shipmentItems.quantity" | jq .

2. Fetch storage place and batch for the article. The warehouse stock provides storagePlaceId and, if needed, batchNumberId.

curl -s -H "AuthenticationToken: $WC_TOKEN" \
  "https://testhandel.weclapp.com/webapp/api/v1/warehouseStock?articleId-eq=16986&warehouseId-eq=13682&includeReferencedEntities=batchNumberId,storagePlaceId" | jq .

3. Set picks. Check existing picks beforehand with GET /shipment/id/{id}/picks.

curl -i -X POST "https://testhandel.weclapp.com/webapp/api/v1/shipment/id/196858/updatePicks" \
  -H "AuthenticationToken: $WC_TOKEN" -H "Content-Type: application/json" \
  -d '{"removeUnconfirmed":true,"updateShipQuantity":true,"useReservedSerialNumbersFromOtherShipments":true,"picks":[{"shipmentItemId":"196867","pickQuantity":1,"storagePlaceId":"13685"}]}'

This is how Fulfillment 4 weclapp builds the body. The IDs are example values, you replace them with the values from steps 1 and 2.

Field Meaning
removeUnconfirmed clears unconfirmed old picks. Needed when a pick changes the storage place.
updateShipQuantity sets the delivery quantity to the picked quantity. A bulk run without quantity changes sends false here.
useReservedSerialNumbersFromOtherShipments allows serial numbers that are already attached to other deliveries.
picks[].id only when updating an existing pick. Omit when changing the storage place, otherwise weclapp rejects it.
picks[].shipmentItemId Required. The item, not the article.
picks[].pickQuantity Required, greater than 0. Number or string, both are accepted.
picks[].storagePlaceId Required for a new pick.
optional batchNumber, batchExpirationDate, serialNumbers: [], levelId, internalTransportReferenceId

The batch and serial number variant looks like this:

curl -i -X POST "https://testhandel.weclapp.com/webapp/api/v1/shipment/id/196858/updatePicks" \
  -H "AuthenticationToken: $WC_TOKEN" -H "Content-Type: application/json" \
  -d '{"removeUnconfirmed":true,"updateShipQuantity":true,"useReservedSerialNumbersFromOtherShipments":true,"picks":[
    {"shipmentItemId":"196867","pickQuantity":2,"storagePlaceId":"13685","batchNumber":"CH-2026-04","serialNumbers":[]},
    {"shipmentItemId":"196868","pickQuantity":1,"storagePlaceId":"13685","serialNumbers":["SN-0001"]}
  ]}'

Two practical notes. First: you don't enable batch-level reservation in weclapp. It blocks switching batches when a batch runs short, and only the fulfiller knows the batch at the pick anyway. Second: if you go straight to SHIPPED without picks, weclapp books the missing withdrawals automatically, but from the default storage place and without your batch. For clean batch tracking, report the picks beforehand.

Step 4: Report shipping: status SHIPPED, carrier, tracking

A tracking number alone is worthless. Only the status change to SHIPPED books the goods issue from the delivery in weclapp, triggers the shipping confirmation to the shop and starts the follow-up processes, such as the invoice. And because as a fulfiller you choose the shipping carrier yourself, you have to write it along: if carrier and tracking number don't match, shops and marketplaces reject the shipping confirmation. Amazon suspends the merchant after repeated errors.

curl --location --request PUT 'https://testhandel.weclapp.com/webapp/api/v2/shipment/id/215289?ignoreMissingProperties=true' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{
    "status": "SHIPPED",
    "packageReferenceNumber": "4-351",
    "packageTrackingNumber": "00340433923095602571",
    "packageWeight": "11.4",
    "shippingCarrierId": "38114"
}'
  • packageReferenceNumber is your own reference, packageWeight the gross weight including packaging.
  • The example applies to shipments with one parcel. For several parcels you write the array parcels[] with carrier and tracking number per parcel. You then omit the single-parcel fields, otherwise weclapp rejects them.
  • Report the status at the actual physical departure, not when the label is created. Otherwise the goods issue collides with your stock reconciliation.
  • One delivery at a time, no batch. If a request runs into a timeout or 409, you read the delivery back before retrying: the first request may long since have been processed server-side. And a delivery that is already SHIPPED you don't ship again.

Step 5: Fetch the purchase order as advance notice

Replenishment starts with the merchant. They order from the supplier in weclapp out of the planning, and the delivery location is your warehouse. As soon as the purchase order is confirmed, it is an advance notice for you: you know which articles arrive at your site in which quantity at which date, before the truck is at the ramp. The purchase order number is the advance notice reference, plannedDeliveryDate the expected date. This is exactly how it works in the EDI integrations we have implemented for logistics partners, there as an advance shipping notice message. With a REST integration you read the purchase orders directly, again only for your warehouse:

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/purchaseOrder?page=1&pageSize=1000&properties=id%2CpurchaseOrderNumber%2Cstatus%2CplannedDeliveryDate%2CsupplierId%2CwarehouseId%2CpurchaseOrderItems.id%2CpurchaseOrderItems.articleId%2CpurchaseOrderItems.quantity%2CpurchaseOrderItems.receivedQuantity%2CpurchaseOrderItems.plannedDeliveryDate%2Carticle%3AarticleNumber%2Carticle%3Aname%2Carticle%3AbatchNumberRequired&includeReferencedEntities=purchaseOrderItems.articleId&warehouseId-eq=2315&status-eq=CONFIRMED' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****'

In weclapp, a purchase order passes through ORDER_ENTRY_IN_PROGRESS, ORDER_ENTRY_COMPLETED and ORDER_DOCUMENTS_PRINTED to CONFIRMED. Only from CONFIRMED is it an advance notice, and only then does weclapp allow a goods receipt against it. If you try earlier, weclapp answers with 400: “It is not possible to create collective incoming goods for the selected purchase orders because one or more purchase orders have not yet been confirmed by the supplier.” receivedQuantity per item tells you what has already arrived, so you can recognize partial deliveries. If the delivery date is less than two working days in the future, a warning in the log makes sense, nothing more: the advance notice goes out anyway.

All requests in steps 5 and 6 ran exactly like this against our test system on 08.09.2026. The IDs in the text are the real values from that run: purchase order P1727 for 100 units of the batch-tracked article K14101 to warehouse 2315.

Step 6: Create the goods receipt against the purchase order

When the goods arrive, you create the goods receipt against the purchase order, not as a loose movement. That closes the loop for the merchant: the purchase order shows the delivered quantity, planning calculates correctly, invoice verification has a document, and the valuation price comes from the purchase price. One goods receipt per physical delivery. If a purchase order arrives in three partial deliveries, that is three goods receipts against the same purchase order. If several purchase orders arrive on one pallet, you attach them to the same goods receipt via additionalPurchaseOrderIds. Five requests, in this order. For a full delivery the second one is omitted.

1. Create the goods receipt from the purchase order. weclapp creates one goods receipt item per purchase order item.

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/purchaseOrder/id/7398324/createIncomingGoods' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{ "additionalPurchaseOrderIds": [] }'

The response is the goods receipt WE1696 (ID 7398350) in status INCOMING_SHIPPED, with one goods receipt item 7398352 per purchase order item, linked via purchaseOrderItemId.

2. Only for partial deliveries: set item quantities to the delivered quantity. createIncomingGoods takes over the complete open purchase order quantity per item. If only 60 of 100 ordered units arrive, you set the item quantity to 60 beforehand. Otherwise putaway fails later with “In item 1 the booking quantity does not match the unbooked item quantity”. Send all items along with their id, missing items would be deleted.

curl --location --request PUT 'https://testhandel.weclapp.com/webapp/api/v2/incomingGoods/id/7398485?ignoreMissingProperties=true' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{ "incomingGoodsItems": [ { "id": "7398487", "quantity": "60" } ] }'

The next createIncomingGoods on the same purchase order then gets only the remaining quantity of 40 as an item. Once the purchase order is fully delivered, weclapp rejects a further goods receipt: “It is not possible to create collective incoming goods because the purchase order P1729 does not contain any open items.”

3. Read the booking lines. weclapp creates a suggestion line per item: item quantity, default storage place and already the valuation price from the purchase order, here 20.00. You need the incomingGoodsItemId from it.

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/incomingGoods/id/7398350/incomingBookings' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****'

4. Book quantities, batches and storage places. One line per item and batch. Two batches for the same item are two lines with the same incomingGoodsItemId. The best-before date is called expirationDate here, again in epoch milliseconds. Lines without id replace the suggestion line; if you want to keep an existing line, you send it along with its id. You carry the articleValuationPrice from the suggestion line into every booking line. That's how the integrations we implemented do it, mirroring every suggestion line field by field. In the test weclapp also inherited the price without it being specified, but being explicit is still safer, and you can override it if the delivery note names a different price than the purchase order.

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/incomingGoods/id/7398350/updateIncomingBookings' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{
  "incomingBookings": [
    { "incomingGoodsItemId": "7398352", "quantity": "60", "batchNumber": "CH-2026-04", "expirationDate": 1882908000000, "storagePlaceId": "2317", "articleValuationPrice": "20" },
    { "incomingGoodsItemId": "7398352", "quantity": "40", "batchNumber": "CH-2026-05", "expirationDate": 1885500000000, "storagePlaceId": "2317", "articleValuationPrice": "20" }
  ]
}'

The response confirms both lines, each with the inherited valuation price 20.00.

5. Put away. Without this status change the goods stay in the state “arrived but not put away” and do not show up in available stock. There is no dedicated action endpoint for this, the status is set via the normal PUT. You don't have to send a version with it. Only this step books the stock, and only here does weclapp check the batch: for a batch-tracked article, updateIncomingBookings still accepts a booking line without a batch, but putaway then fails with “Batch number is missing for booking records of item 1”.

curl --location --request PUT 'https://testhandel.weclapp.com/webapp/api/v2/incomingGoods/id/7398350?ignoreMissingProperties=true' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{ "status": "INCOMING_MOVED_INTO_STORE" }'

And this is what it looks like in weclapp afterwards, read back via API:

Object Result after step 6
Goods receipt WE1696 Status INCOMING_MOVED_INTO_STORE, warehouse 2315
Purchase order P1727 receivedQuantity 100 of 100, status stays CONFIRMED
Warehouse stock K14101 60 units batch CH-2026-04 and 40 units batch CH-2026-05 on storage place 2317, each with best-before date
Stock movements two bookings of type IN_PURCHASE_ORDER with valuationPrice 20.00 and reference to the goods receipt item
Partial delivery P1729 two goods receipts against the same purchase order: WE1697 with 60 units, WE1698 with 40 units, receivedQuantity first 60, then 100

The valuation price came from the purchase order without any extra effort, the movement type is “goods receipt from purchase order”, and planning sees the purchase order as delivered. This is exactly what the merchant never gets with a stocktake sync.

Goods without a purchase order

Samples, follow-up deliveries without a document, returns from a promotion: not everything that arrives at your site has a purchase order. For that there is bookIncomingMovement, and it takes everything that batch-tracked goods need: batch number, best-before date, target storage place and the valuation price.

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/warehouseStockMovement/bookIncomingMovement' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{
  "articleId": "36782",
  "batchNumber": "12345",
  "batchNumberExpirationDate": 1883208663000,
  "quantity": "10",
  "movementNote": "Wareneingang ohne Bestellung, Lieferschein 4711",
  "targetStoragePlaceId": "2317",
  "articleValuationPrice": "10"
}'

The valuation price is not a nice-to-have here. Without articleValuationPrice the merchant's stock valuation is broken, and nobody likes repairing that retroactively. For a goods receipt against the purchase order, the purchase price comes with the suggestion line; for the loose movement you have to supply it yourself: articleValuationPrice is then the purchase price. You won't find it on the article but on the primary supply source. The article request from step 1 therefore pulls it in via articleSupplySource:articlePrices. Re-read it regularly, you don't synchronize supplier master data.

“You don't get the purchase price on the same level as the article, but via the supply sources.”
Markus Wals, weclapp API Deep Dive for fulfillers

Step 7: Reconcile stock: book the difference, never a stocktake

The periodic reconciliation is a correction mechanism for real differences, not a stock export. You read the weclapp stock of your warehouse per article, storage place and batch, including the open picks:

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/warehouseStock?pageSize=1000&properties=id%2CarticleId%2Cpicks%2Cquantity%2CbatchNumberId%2CstoragePlaceId%2Carticle%3Aid%2Carticle%3AarticleNumber%2CbatchNumber%3Aid%2CbatchNumber%3AbatchNumber%2Cpick%3Aid%2Cpick%3AshipmentItemId%2Cpick%3Aquantity&includeReferencedEntities=picks.id%2CbatchNumberId&warehouseId-eq=2315' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****'

The freely available stock per article is quantity minus the sum of the picks. weclapp does not provide a field for this, you calculate it yourself. That is exactly why the reconciliation only runs after all shipping notifications have been processed, and excludes quantities that you have already picked but not yet reported as shipped. Otherwise you book against running orders and create exactly the back and forth that the merchant sees in their stock movements.

You book the difference as a movement, with a note that identifies the run:

curl --location 'https://testhandel.weclapp.com/webapp/api/v2/warehouseStockMovement/bookOutgoingMovement' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --header 'AuthenticationToken: ****' \
  --data '{
  "articleId": "36782",
  "batchNumber": "1220",
  "movementNote": "Bestandsabgleich Fulfiller 2026-09-08 06:00",
  "quantity": "8819.8",
  "sourceStoragePlaceId": "2317"
}'

For receipts without a purchase order reference, you use bookIncomingMovement from step 6, with valuation price. You book movements one article after another, not in parallel, otherwise they collide on the stock version. After a reset of the movement data before go-live, the first run books the complete physical stock as a goods receipt. That is then the clean opening stock.

Why not the stocktake?

Because it was built for something else. According to the manufacturer, the weclapp stocktake is a key-date stocktake. While it runs, the warehouse is locked and only released for picking again after completion. It books differences as its own movement type “goods receipt or issue from stocktake”. A connector that creates stocktaking documents several times a day locks the warehouse on every run, turns every goods receipt into an inventory difference and floods the history the merchant needs for their annual stocktake and the tax advisor. The movement endpoints exist for exactly this case.

Batch tracking throughout, serial numbers as an extension

Batches run through all seven touchpoints, and weclapp is well suited for this, especially for food and beverages:

  • Article: batchNumberRequired tells you which articles need a batch. For goods receipt, weclapp checks this at the moment stock is created, i.e. at putaway. The same applies to picks: batch-tracked articles need the batch on the pick.
  • Goods receipt: one booking line per batch with batchNumber and expirationDate, for the loose movement batchNumber plus batchNumberExpirationDate. From now on the best-before date can be evaluated in weclapp, for example for coverage and expiry lists.
  • Picks: the batch actually withdrawn per item. Your FEFO logic runs in the WMS, weclapp receives the result.
  • Reconciliation: the difference per article and batch, not just per article. The stock from step 7 delivers the batch via batchNumber:batchNumber.

Serial numbers work by the same pattern, just one level deeper: serialNumberRequired on the article, serialNumbers[] in picks and movements, and on the pick the switch useReservedSerialNumbersFromOtherShipments in case a number is already attached to another delivery. One rule on this from our integrations: within a goods receipt, every serial number must be unique, even across several batches of the same item, otherwise weclapp rejects the putaway (“The same serial number is already selected in another booking item for this article in position N”). If you have batches under control, you get serial numbers on top in an afternoon.

What you don't have to build

  • Cancellations: by email for now. A complete technical cancellation chain costs more than it brings in the first few months. If the goods are already on their way, a return and reshipment is cheaper than a halted process.
  • Feedback to the merchant's supplier: the purchase order remains the merchant's business. You report what has arrived, nothing more.
  • Supplier master data: don't synchronize it. You read the purchase price, nothing more.
  • Key figures via additionalProperties: these are display values, not a basis for bookings. Bookings are made via warehouse stock and movements.

Go-live checklist

  1. Develop against a test system, never against the merchant's production tenant.
  2. Run through the complete cycle once: confirm purchase order, read advance notice, goods receipt in two partial deliveries, sales order, picks with batch, shipping, reconciliation without difference.
  3. Run through one positive case, one negative case and the error path for each data flow direction. The negative cases from our test run: goods receipt on an unconfirmed purchase order (400, “not yet been confirmed by the supplier”), putaway without batch (400, “Batch number is missing”), goods receipt on a fully delivered purchase order (400, “does not contain any open items”). Plus: a delivery from another warehouse is ignored, a timeout on shipping leads to a read-back instead of a duplicate shipment.
  4. status == success is not proof. Proof is the target object in weclapp with the right field values, read back via API. wals.pro AI 4 weclapp does exactly this read-back for you on the test system.
  5. Before go-live, have the tenant's movement data reset and switch off the stocktake or test sync beforehand, otherwise the system is immediately full again after the reset.
  6. In the first hour after go-live, check the real runs for content: movement types, batches, shipping status, shipping confirmation in the shop.

Tools that speed up the integration

Three things decide whether such an integration takes two weeks or two months.

A test system

weclapp offers its own test systems. They are not paid per user but as a flat rate per system and month, and that is cheaper than a single license in the production system. That pays off from day one: you never develop on your customer's production tenant, you can have movement data reset at any time, and your end-to-end tests run against real document chains instead of mocks. If you don't want your own test system, we provide one including master data and support.

Basic training and Postman collection

If you are new to the weclapp API, start with our webinar weclapp API Deep Dive on YouTube: authentication, filters, projection, referenced entities and the document chain, in one hour. The requests from this article and many more are available ready-made in our Postman Collection 4 weclapp, with environment variables for test system and tenant.

weclapp Toolbox

The Chrome extension weclapp Toolbox opens the API view for every record in the interface, copies IDs and exports requests as cURL. Every ID in this article was created that way.

wals.pro AI 4 weclapp

The AI platform knows the schema and API documentation of your tenant and reads data back. That makes it the most important tool next to the Toolbox in all three phases:

  • Planning: Your assistant finds the IDs you need: warehouse, storage places, carriers, custom field definitions and their values. No copy-paste from the interface.
  • Development: It answers field names, filters, required fields and endpoints from schema and docs, instead of you searching the OpenAPI.
  • Validation: It runs the end-to-end scenarios on the test system and reads the results back. Is the delivery really SHIPPED, is the batch on the goods receipt, is the stock correct after the reconciliation. Every write action runs through preview and approval.

What this looks like in practice with stock bookings is shown in the article Booking goods receipt and shipping with AI.

Training

And if you don't want to build the integration alone: we train fulfillers every week on exactly this process, with the test system and the requests from this article.

👉 Book training or an intro call: wals.pro/termin
👉 Start for free, open beta until 31.12.2026: ai.wals.pro