ecomone developers
Open Ecomone
Docs / Fulfillment & logistics

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#

  1. Get orders

    Receive order.paid webhooks, or poll paid, unfulfilled orders.

  2. Ship

    Set the fulfillment status and tracking. Ecomone e-mails the customer.

  3. Sync stock

    Push absolute stock levels or adjustments per SKU.

  4. Stay in sync

    Pick up cancellations and refunds via webhooks or updated_since.

Scopes you need#

ScopeWhy
orders:readRead orders, line items and shipping addresses
orders:fulfillmentSet fulfillment status and tracking
inventory:readRead stock levels
inventory:writeWrite stock levels
products:readMap SKUs to products and variants (optional)
i

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
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.

Order (excerpt)
{
  "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
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 shipped or fulfilled. 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": false to suppress e-mails.
  • Cancelled or refunded orders can't be shipped (409 conflict).
  • Unknown fields or invalid statuses return 400 and nothing is changed.
  • Every status change to shipped or fulfilled — through the API, the Ecomone backend or MCP — sends the order.shipped or order.fulfilled webhook.

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.

Request
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}]}'
200Response
{
  "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 errors lists each problem with its index. Fix the entries and send the batch again.
  • Send an Idempotency-Key header. 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_id and variant_id.

4. Stay in sync#

  • Cancellations and refunds: subscribe to order.cancelled and order.refunded and stop or reverse the shipment. Use the is_canceled flag: a paid order cancelled with a full refund ends with status: "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 with cursor, then store the largest updated_at you have seen for the next call. Any change to an order counts, so compare the fields you care about.

Fulfillment statuses#

ValueMeaning
unfulfilledNot processed yet
processingBeing picked and packed
partialPart of the order has been shipped
shippedHanded to the carrier — triggers the shipping e-mail
fulfilledCompleted — triggers the shipping e-mail if not sent yet
deliveredDelivered to the customer
i

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 429 with Retry-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.

Authorize requests

Requests go straight from your browser to https://api.ecomone.shop. Your credentials stay in this browser tab and are never sent to this documentation site.