Fulfillment & logistics
Connect a warehouse, 3PL or shipping tool to an Ecomone shop. This guide walks through the complete loop: receive paid orders, ship them with tracking, keep stock levels in sync and handle cancellations.
Overview#
Get orders
Receive
order.paidwebhooks, or poll paid, unfulfilled orders.Ship
Set the fulfillment status and tracking. Ecomone e-mails the customer.
Sync stock
Push absolute stock levels or adjustments per SKU.
Stay in sync
Pick up cancellations and refunds via webhooks or
updated_since.
Scopes you need#
| Scope | Why |
|---|---|
orders:read | Read orders, line items and shipping addresses |
orders:fulfillment | Set fulfillment status and tracking |
inventory:read | Read stock levels |
inventory:write | Write stock levels |
products:read | Map SKUs to products and variants (optional) |
Writing stock requires that the person who created the API key (or approved your app) can manage the shop. Keys created by the shop owner always can.
1. Get orders to ship#
The fastest way is the order.paid webhook. As a fallback (or if you prefer polling), list paid orders that are not fulfilled yet:
curl "https://api.ecomone.shop/api/public/v1/orders?status=paid&fulfillment_status=unfulfilled&limit=50" \
-H "X-Api-Key: ek_..." -H "X-Api-Secret: sk_..."Every order contains line_items in a stable format and a normalized shipping_address_normalized — use these instead of the raw items and shipping_address fields, whose shape can vary between older orders.
{
"id": "9f1c2b7e-4a55-4d1f-9b62-1c0a3f6d8e21",
"order_number": 1042,
"status": "paid",
"fulfillment_status": "unfulfilled",
"updated_at": "2026-11-13T09:14:25.000Z",
"line_items": [
{
"id": "l_1",
"product_id": "6b1f8a2e-0c4d-4e1a-9f3b-2d5c7e9a1b20",
"variant_id": "size-m",
"title": "Thermo socks",
"variant_title": "M",
"sku": "TS-M",
"quantity": 2,
"unit_price_cents": 1990,
"total_cents": 3980,
"requires_shipping": true
}
],
"shipping_address_normalized": {
"name": "Anna Muster",
"company": null,
"line1": "Bahnhofstrasse 1",
"line2": null,
"postal_code": "8001",
"city": "Zürich",
"state": null,
"country": "CH",
"phone": "+41 79 000 00 00"
}
}Line items with requires_shipping: false are digital products and need no shipment.
2. Mark as shipped#
curl -X PATCH "https://api.ecomone.shop/api/public/v1/orders/1042" \
-H "X-Api-Key: ek_..." -H "X-Api-Secret: sk_..." \
-H "Content-Type: application/json" \
-d '{
"fulfillment_status": "shipped",
"tracking_carrier": "Swiss Post",
"tracking_number": "99.60.123456.12345678",
"tracking_url": "https://service.post.ch/ekp-web/ui/entry/search/99.60.123456.12345678"
}'- You can address an order by its UUID or by its order number.
- The customer receives the shop's shipping e-mail when the status changes to
shippedorfulfilled. Ideally send the tracking number in the same request; if you add or change it later, the customer gets a tracking update e-mail (once per tracking number). Send"notify_customer": falseto suppress e-mails. - Cancelled or refunded orders can't be shipped (
409 conflict). - Unknown fields or invalid statuses return
400and nothing is changed. - Every status change to
shippedorfulfilled— through the API, the Ecomone backend or MCP — sends theorder.shippedororder.fulfilledwebhook.
3. Sync stock levels#
Send up to 200 entries per request. Address a product or variant by sku, or by product_id (plus variant_id for variants). Use stock for an absolute level or adjust for a relative change. The batch is one transaction: either all entries are written or none.
curl -X POST "https://api.ecomone.shop/api/public/v1/inventory" \
-H "X-Api-Key: ek_..." -H "X-Api-Secret: sk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sync-2026-11-14T09:00" \
-d '{"items":[{"sku":"TS-M","stock":120},{"sku":"TS-L","adjust":-3}]}'{
"data": [
{
"product_id": "6b1f8a2e-0c4d-4e1a-9f3b-2d5c7e9a1b20",
"variant_id": "size-m",
"sku": "TS-M",
"stock": 120
},
{
"product_id": "6b1f8a2e-0c4d-4e1a-9f3b-2d5c7e9a1b20",
"variant_id": "size-l",
"sku": "TS-L",
"stock": 41
}
],
"not_found": [],
"errors": []
}- If any entry has a problem (unknown or ambiguous SKU, invalid value), nothing is written and
errorslists each problem with itsindex. Fix the entries and send the batch again. - Send an
Idempotency-Keyheader. If the network fails and you retry with the same key and entries, the change is applied only once (keys are kept for 24 hours). - Keep SKUs unique, or address items by
product_idandvariant_id.
4. Stay in sync#
- Cancellations and refunds: subscribe to
order.cancelledandorder.refundedand stop or reverse the shipment. Use theis_canceledflag: a paid order cancelled with a full refund ends withstatus: "refunded". - Polling:
GET /orders?updated_since=<timestamp>returns every order changed since then. Results are still sorted by creation date, so page through all results withcursor, then store the largestupdated_atyou have seen for the next call. Any change to an order counts, so compare the fields you care about.
Fulfillment statuses#
| Value | Meaning |
|---|---|
unfulfilled | Not processed yet |
processing | Being picked and packed |
partial | Part of the order has been shipped |
shipped | Handed to the carrier — triggers the shipping e-mail |
fulfilled | Completed — triggers the shipping e-mail if not sent yet |
delivered | Delivered to the customer |
One order has one tracking number. Split shipments with several parcels are not supported yet. Bundle lines with several variants appear as one line item per unit, linked by bundle_line_id.
Go-live checklist#
- Verify the webhook signature and de-duplicate on
event_id. - Handle
429withRetry-After(600 requests per minute per key). - Test with orders marked as test orders in the merchant's shop before going live.
- Store the API secret or OAuth tokens encrypted on your server only.