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

# Cart Webhook

> Complete specification for the cart webhook. Handle add, remove, update, and clear operations from the AI assistant.

The cart webhook is called when the AI assistant performs a cart operation on behalf of the customer. Your endpoint receives the operation details and returns a success/failure response.

***

## Request Format

**Method**: `POST`

**Headers**:

| Header                 | Value                    |
| ---------------------- | ------------------------ |
| `Content-Type`         | `application/json`       |
| `X-VIZOCHOK-Signature` | `sha256=<hex_digest>`    |
| `X-VIZOCHOK-Timestamp` | Unix timestamp (seconds) |

**Body**:

```json theme={null}
{
  "action": "add",
  "store_id": "store-1",
  "session_id": "user-123",
  "sku": "milk-001",
  "name": "Молоко Lactel 2.5%",
  "quantity": 1,
  "price": 45.90
}
```

### Common Fields

<ParamField body="action" type="string" required>
  The cart operation: `"add"`, `"remove"`, `"update_quantity"`, or `"clear"`.
</ParamField>

<ParamField body="store_id" type="string" required>
  Store identifier.
</ParamField>

<ParamField body="session_id" type="string" required>
  Session/user identifier. Matches the `userId` from the widget config, or an auto-generated ID if none was provided. Use this to look up the correct cart.
</ParamField>

***

## Actions

### Add

Add a product to the cart.

```json theme={null}
{
  "action": "add",
  "store_id": "store-1",
  "session_id": "user-123",
  "sku": "milk-001",
  "name": "Молоко Lactel 2.5%",
  "quantity": 1,
  "price": 45.90
}
```

<ParamField body="sku" type="string" required>
  Product SKU to add.
</ParamField>

<ParamField body="name" type="string">
  Product name (for convenience -- you can ignore this and look up by SKU).
</ParamField>

<ParamField body="quantity" type="number">
  Quantity to add. Defaults to 1 if not provided.
</ParamField>

<ParamField body="price" type="number">
  Price as known by the AI (from the last products webhook call). You may use this or look up the current price yourself.
</ParamField>

### Remove

Remove a product from the cart.

```json theme={null}
{
  "action": "remove",
  "store_id": "store-1",
  "session_id": "user-123",
  "sku": "milk-001"
}
```

<ParamField body="sku" type="string" required>
  Product SKU to remove.
</ParamField>

### Update Quantity

Change the quantity of an existing cart item.

```json theme={null}
{
  "action": "update_quantity",
  "store_id": "store-1",
  "session_id": "user-123",
  "sku": "milk-001",
  "quantity": 3
}
```

<ParamField body="sku" type="string" required>
  Product SKU to update.
</ParamField>

<ParamField body="quantity" type="number" required>
  New quantity for the product.
</ParamField>

### Clear

Remove all items from the cart.

```json theme={null}
{
  "action": "clear",
  "store_id": "store-1",
  "session_id": "user-123"
}
```

No additional fields required.

***

## Response Format

Return a JSON object with `ok` (boolean) and an optional `reason` on failure:

### Success

```json theme={null}
{"ok": true}
```

### Failure

```json theme={null}
{"ok": false, "reason": "out_of_stock"}
```

<ResponseField name="ok" type="boolean" required>
  Whether the operation succeeded.
</ResponseField>

<ResponseField name="reason" type="string">
  Human-readable reason for failure. The AI uses this to inform the customer and may suggest alternatives.
</ResponseField>

### Common Rejection Reasons

| Reason              | When to Return                              | AI Behavior                              |
| ------------------- | ------------------------------------------- | ---------------------------------------- |
| `out_of_stock`      | Product is unavailable                      | Suggests alternative products            |
| `product_not_found` | SKU does not exist                          | Informs customer, offers to search again |
| `quantity_exceeded` | Not enough stock for requested quantity     | Suggests a lower quantity                |
| `not_in_cart`       | Trying to remove/update an item not in cart | Informs customer                         |
| `missing_sku`       | No SKU provided                             | Reports error                            |
| `missing_params`    | Required parameters missing                 | Reports error                            |

<Info>
  You can use any string as the `reason` value. The AI reads the reason and responds contextually. Common, descriptive reasons work best -- for example, `"max_3_per_customer"` would lead the AI to say something like "There's a limit of 3 per customer for this product."
</Info>

***

## Cart GET Endpoint

In addition to the POST webhook for operations, you can implement a GET endpoint to provide the initial cart state when a new conversation starts.

**Method**: `GET`

**Query Parameters**:

| Parameter    | Description             |
| ------------ | ----------------------- |
| `store_id`   | Store identifier        |
| `session_id` | Session/user identifier |

**Response**:

```json theme={null}
{
  "items": [
    {
      "sku": "bread-01",
      "name": "Хліб Столичний",
      "price": 32.00,
      "quantity": 1,
      "unit": "pcs"
    },
    {
      "sku": "butter-05",
      "name": "Масло Президент 200г",
      "price": 89.50,
      "quantity": 1,
      "unit": "pcs",
      "promo_price": 79.90
    }
  ]
}
```

<ResponseField name="items" type="array" required>
  Array of cart items.

  <ResponseField name="sku" type="string" required>
    Product SKU.
  </ResponseField>

  <ResponseField name="name" type="string" required>
    Product name.
  </ResponseField>

  <ResponseField name="price" type="number" required>
    Unit price.
  </ResponseField>

  <ResponseField name="quantity" type="number" required>
    Quantity in cart.
  </ResponseField>

  <ResponseField name="unit" type="string">
    Unit of measure (e.g., `"pcs"`, `"kg"`). Defaults to `"PCS"`.
  </ResponseField>

  <ResponseField name="promo_price" type="number">
    Promotional price, if applicable.
  </ResponseField>
</ResponseField>

***

## Handler Examples

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  from fastapi import FastAPI, Request, HTTPException
  from pydantic import BaseModel

  app = FastAPI()

  class CartWebhookRequest(BaseModel):
      action: str
      store_id: str
      session_id: str
      sku: str | None = None
      name: str | None = None
      quantity: int | None = None
      price: float | None = None


  @app.post("/api/vizochok/cart")
  async def handle_cart(req: CartWebhookRequest, request: Request):
      body = await request.body()
      verify_signature(request, body)  # See signature verification docs

      cart = get_cart(req.session_id)

      if req.action == "add":
          if not req.sku:
              return {"ok": False, "reason": "missing_sku"}

          product = db.get_product(req.sku)
          if not product or product.stock <= 0:
              return {"ok": False, "reason": "out_of_stock"}

          cart.add_item(
              sku=req.sku,
              name=req.name or product.name,
              price=product.price,  # Use YOUR price, not the one from the request
              quantity=req.quantity or 1,
          )
          return {"ok": True}

      elif req.action == "remove":
          if not req.sku:
              return {"ok": False, "reason": "missing_sku"}
          cart.remove_item(req.sku)
          return {"ok": True}

      elif req.action == "update_quantity":
          if not req.sku or not req.quantity:
              return {"ok": False, "reason": "missing_params"}

          product = db.get_product(req.sku)
          if product and product.stock < req.quantity:
              return {"ok": False, "reason": "quantity_exceeded"}

          cart.update_quantity(req.sku, req.quantity)
          return {"ok": True}

      elif req.action == "clear":
          cart.clear()
          return {"ok": True}

      return {"ok": False, "reason": "unknown_action"}


  @app.get("/api/vizochok/cart/items")
  async def get_cart_items(store_id: str = "", session_id: str = ""):
      cart = get_cart(session_id)
      return {"items": cart.get_items()}
  ```

  ```javascript Node.js (Express) theme={null}
  const express = require('express');
  const app = express();

  app.use('/api/vizochok', express.json({
    verify: (req, res, buf) => { req.rawBody = buf; }
  }));

  app.post('/api/vizochok/cart', async (req, res) => {
    try {
      verifySignature(req); // See signature verification docs
    } catch (err) {
      return res.status(401).json({ error: err.message });
    }

    const { action, store_id, session_id, sku, name, quantity, price } = req.body;
    const cart = getCart(session_id);

    switch (action) {
      case 'add': {
        if (!sku) return res.json({ ok: false, reason: 'missing_sku' });

        const product = await db.getProduct(sku);
        if (!product || product.stock <= 0) {
          return res.json({ ok: false, reason: 'out_of_stock' });
        }

        cart.addItem({
          sku,
          name: name || product.name,
          price: product.price,
          quantity: quantity || 1,
        });
        return res.json({ ok: true });
      }

      case 'remove':
        if (!sku) return res.json({ ok: false, reason: 'missing_sku' });
        cart.removeItem(sku);
        return res.json({ ok: true });

      case 'update_quantity':
        if (!sku || !quantity) {
          return res.json({ ok: false, reason: 'missing_params' });
        }
        cart.updateQuantity(sku, quantity);
        return res.json({ ok: true });

      case 'clear':
        cart.clear();
        return res.json({ ok: true });

      default:
        return res.json({ ok: false, reason: 'unknown_action' });
    }
  });

  app.get('/api/vizochok/cart/items', (req, res) => {
    const { session_id } = req.query;
    const cart = getCart(session_id);
    res.json({ items: cart.getItems() });
  });
  ```
</CodeGroup>

***

## Best Practices

<Tip>
  Always validate stock on `add` operations. The AI has price data from the last products webhook call, but stock may have changed since then.
</Tip>

<Warning>
  Use your own authoritative price when adding items to the cart, not the `price` field from the webhook request. The price in the request is what VIZOCHOK received from your last products webhook call and may be slightly stale.
</Warning>

* **Idempotency**: If the same `add` request arrives twice (due to retries), your handler should handle it gracefully -- either add the quantity again or deduplicate.
* **Session mapping**: The `session_id` maps to the `userId` from the SDK. If no `userId` was provided, VIZOCHOK generates one per WebSocket connection. Plan your cart storage accordingly.
* **Response time**: Keep responses under 5 seconds (the default timeout). Cart operations should be fast since they are blocking the AI's response to the customer.
