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.
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
| Field | Required | Notes |
|---|---|---|
external_id | yes | Your order id. Natural key with store_id. |
placed_at | yes | ISO 8601 datetime. |
total_price | yes | Number ≥ 0. |
currency | no | 3-letter ISO code. |
financial_status | no | Free string (e.g. pending, paid, refunded). |
fulfillment_status | no | Free string (e.g. unfulfilled, fulfilled). |
customer | yes | Object. customer.phone is required. |
line_items | no | Array of objects. |
tags | no | Array of strings. |
metadata | no | Free-form object. |
2. Mark it paid
When payment clears, patch just the status — no need to resend the whole order.
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.
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:
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.