> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whatmore.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Order Tracking

When an order completes, your backend reports it to Whatmore so purchases can be attributed
to the videos that drove them. This is a single authenticated call.

## Endpoint

```http theme={null}
POST https://api.whatmore.live/external-shop-order-tracking/private?store_id=<store_id>
Authorization: Bearer <access_token>
Content-Type: application/json
```

## Payload

```json theme={null}
{
  "order_id": "ORD-10293",
  "order_items": [
    { "product_id": "CN8490-002", "item_id": "LI-1", "sku": "CN8490-002", "price": "130.00", "quantity": 1, "currency": "USD" },
    { "product_id": "DA1234-100", "item_id": "LI-2", "sku": "DA1234-100", "price": "90.00",  "quantity": 2, "currency": "USD" }
  ],
  "whatmore_video_view":  "[{\"product_id\":\"CN8490-002\",\"widget_info\":\"carousel_84213\"}]",
  "whatmore_add_to_cart": "[{\"product_id\":\"CN8490-002\",\"widget_info\":\"carousel_84213\"}]"
}
```

| Field                  | Type                      | Notes                                                                                                                                                                                                       |
| ---------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `order_id`             | string                    | Your order identifier. Used for idempotency (see below).                                                                                                                                                    |
| `order_items[]`        | array                     | One entry per line item — see the table below.                                                                                                                                                              |
| `whatmore_video_view`  | **string** (JSON-encoded) | Products **watched in a video**. A JSON-encoded array of `{ product_id, widget_info }`. On web the widget stores it in `localStorage._whatmore_viewed_products`; pass it through as-is. Defaults to `"[]"`. |
| `whatmore_add_to_cart` | **string** (JSON-encoded) | Same shape, for products **added to cart from a video**. On web, `localStorage._whatmore_add_to_cart_products`. Defaults to `"[]"`.                                                                         |

Each `order_items[]` entry:

| Field        | Type              | Required | Notes                                                                   |
| ------------ | ----------------- | -------- | ----------------------------------------------------------------------- |
| `product_id` | string            | **Yes**  | Your `client_product_id` — must match your catalog (see warning below). |
| `price`      | string            | **Yes**  | Unit price as a decimal string (`"130.00"`).                            |
| `quantity`   | integer           | **Yes**  | Units ordered.                                                          |
| `currency`   | string (ISO 4217) | **Yes**  | e.g. `"USD"`.                                                           |
| `item_id`    | string            | Optional | Your line-item id.                                                      |
| `sku`        | string            | Optional | Stock-keeping unit.                                                     |

### What's inside the video-view / add-to-cart signals

`whatmore_video_view` and `whatmore_add_to_cart` are **JSON-encoded strings** (not objects) —
the App SDK / widget produces them and you pass them straight through from `localStorage`. You
don't build these by hand. Decoded, the string is an array of small objects:

```json theme={null}
[
  { "product_id": "CN8490-002", "widget_info": "carousel_84213" }
]
```

| Field         | Type   | Notes                                                                                                                                                                                                                       |
| ------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `product_id`  | string | The `client_product_id` that was viewed / added from a video.                                                                                                                                                               |
| `widget_info` | string | Identifies the surface + video credited, formatted `<widget_type>_<event_id>` — e.g. `carousel_84213`. `widget_type` is one of `carousel`, `stories`, `collection`, `banner`, `embed`; `event_id` is the Whatmore video id. |

<Warning>
  The `product_id` you send in `order_items[]` **must be the same identifier your catalog uses
  for that product** (your `client_product_id`) — otherwise the item can't be matched to the
  video signal.
</Warning>

A successful call returns **HTTP 200** with an empty body (`{}`). An item that matches no
video signal is simply left unattributed — it is **not** an error.

## Ready-to-use snippet (web)

On a web storefront the video widget records viewed / added-to-cart products into
`localStorage`. Call this once on your order-confirmation page — it reads those signals and
reports the order:

```javascript theme={null}
async function sendOrderTrackingRequest({ orderId, orderItems, storeId, token }) {
  const url = `https://api.whatmore.live/external-shop-order-tracking/private?store_id=${encodeURIComponent(storeId)}`;
  const payload = {
    order_id: orderId,
    order_items: orderItems,
    whatmore_video_view: localStorage._whatmore_viewed_products || "[]",
    whatmore_add_to_cart: localStorage._whatmore_add_to_cart_products || "[]",
  };
  const res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
    body: JSON.stringify(payload),
  });
  if (!res.ok) throw new Error(`Order tracking failed: ${res.status} ${res.statusText}`);
  return res;
}
```

Build `orderItems` from your order (`product_id`, `item_id`, `sku`, `price`, `quantity`,
`currency` per line), then call `sendOrderTrackingRequest({ orderId, orderItems, storeId, token })`.

## How attribution works

* Whatmore matches each **order item** to the SDK signals by `product_id` (your
  `client_product_id`).
* Attribution is therefore **per line item** — items driven by a video are attributed;
  items bought independently are not. A single order can mix both.
* `whatmore_video_view` and `whatmore_add_to_cart` carry the `widget_info` that identifies
  which surface/video is credited.

## Idempotency & retries

* Orders are de-duplicated by `order_id`. If an order is submitted twice, the duplicate is
  rejected with **HTTP 404 (`Order Id already exists.`)** rather than double-counted.
* This makes retries safe: re-sending the same `order_id` after a network failure cannot
  create a duplicate. Use a stable `order_id` and treat the duplicate response as success.
* Full status-code list is on [Errors & Conventions](/integrations/errors).

<Info>
  Send one tracking call per completed order. The `whatmore_video_view` / `whatmore_add_to_cart`
  strings are produced by the App SDK and passed through your checkout — see
  [App SDK → attribution](/integrations/app-sdk#the-integration-model).
</Info>

## Cart tracking *(optional)*

Cart events can additionally be reported to power funnel analytics between video view and
purchase. Nice-to-have, not required for attribution — scope confirmed during onboarding.
