KanalKanal API
Recipes

Push orders end-to-end

A complete walkthrough of sending an order and its lifecycle.

This recipe shows the full lifecycle of an order: create it, then update its financial and fulfillment status as it progresses — the typical custom-shop integration.

1. Create the order

Send the full order when it's placed. The customer.phone is required and is how Kanal matches the order to a WhatsApp contact.

POST /api/v1/stores/{store_id}/orders
curl -X POST https://api.getkanal.com/api/v1/stores/123/orders \
  -H "Authorization: Bearer kanal_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "ORDER-1001",
    "placed_at": "2026-05-18T10:00:00Z",
    "total_price": 49.90,
    "currency": "EUR",
    "financial_status": "pending",
    "fulfillment_status": "unfulfilled",
    "customer": {
      "external_id": "CUST-42",
      "phone": "+33612345678",
      "first_name": "Marie",
      "last_name": "Durand",
      "email": "[email protected]",
      "locale": "fr",
      "tags": ["vip"],
      "lifetime_value": 1299,
      "orders_count": 7
    },
    "line_items": [
      { "title": "T-shirt Bordeaux", "quantity": 2, "price": 24.95 }
    ],
    "tags": ["web"],
    "metadata": { "source": "custom-checkout" }
  }'

A 201 means the order was created; a 200 means an order with that external_id already existed and was updated. See Idempotency.

Field reference

FieldRequiredNotes
external_idyesYour order id. Natural key with store_id.
placed_atyesISO 8601 datetime.
total_priceyesNumber ≥ 0.
currencyno3-letter ISO code.
financial_statusnoFree string (e.g. pending, paid, refunded).
fulfillment_statusnoFree string (e.g. unfulfilled, fulfilled).
customeryesObject. customer.phone is required.
line_itemsnoArray of objects.
tagsnoArray of strings.
metadatanoFree-form object.

2. Mark it paid

When payment clears, patch just the status — no need to resend the whole order.

PATCH /api/v1/stores/{store_id}/orders/{external_id}
curl -X PATCH https://api.getkanal.com/api/v1/stores/123/orders/ORDER-1001 \
  -H "Authorization: Bearer kanal_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "financial_status": "paid" }'

3. Ship it

Create a shipment linked to the order via order_external_id. This is what triggers shipping-notification automations.

POST /api/v1/stores/{store_id}/shipments
curl -X POST https://api.getkanal.com/api/v1/stores/123/shipments \
  -H "Authorization: Bearer kanal_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "SHIP-9001",
    "order_external_id": "ORDER-1001",
    "tracking_number": "1Z999AA10123456784",
    "tracking_url": "https://track.example.com/1Z999AA10123456784",
    "carrier": "Colissimo",
    "status": "in_transit"
  }'

status must be one of pending, in_transit, delivered, failed, returned.

4. (Optional) push the abandoned cart

If checkout is abandoned before the order exists, send a cart so recovery flows can fire:

POST /api/v1/stores/{store_id}/carts
curl -X POST https://api.getkanal.com/api/v1/stores/123/carts \
  -H "Authorization: Bearer kanal_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CART-7700",
    "abandoned_at": "2026-05-18T09:40:00Z",
    "recovery_url": "https://shop.example.com/cart/recover/7700",
    "total_price": 49.90,
    "currency": "EUR",
    "customer": { "phone": "+33612345678", "first_name": "Marie" }
  }'

That's the whole integration. Everything is upsert + idempotent, so you can replay any step safely.

On this page