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

# Events

> Send analytics events for widget usage and conversion tracking

Send batched analytics events to track widget usage and conversion funnels. Max 50 events per batch. Tracking failures never block user flow.

<Note>
  On JavaScript, the [`@genlook/storefront` SDK](/docs/virtual-tryon/sdk) is the recommended integration: it calls this endpoint for you, along with the rest of the try-on flow.
</Note>

With the SDK, funnel events are assembled, batched, and sent automatically; switch them off with `tracking: "denied"` when creating the client.

## Request

<ParamField body="events" type="array" required>
  Array of event objects. Each contains:

  * `event` (string, required): Event name (e.g. `widget:button_click`)
  * `properties` (object, optional): Custom properties
  * `timestamp` (string, required): ISO 8601 timestamp
  * `$insert_id` (string, required): Unique ID for deduplication
</ParamField>

<ParamField body="context" type="object" required>
  Shared context for all events in the batch. Required fields: `anonymous_id` (`anon_*`), `session_id` (`sess_*`), `$pageview_id`, `widget_version` (`wv_*`), `widget_enabled`, `product_id` and `variant_id` (both nullable), `$screen_width`, `$screen_height` (send `0` when there is no display), and `$timezone`.

  The page fields (`$current_url`, `$pathname`, `$host`, `$viewport_width`, `$viewport_height`, `$raw_user_agent`, `$browser_language`) are optional and nullable: a browser fills them all, a native app or a server has none of them, and an honest `null` is expected there. Never invent values for them.

  An `integration` field (optional string) labels where the batch came from, e.g. `"sdk"`. Set it when you build batches yourself so your traffic can be told apart in analytics.
</ParamField>

<Note>
  The [Genlook SDK](/docs/virtual-tryon/sdk) assembles and sends all of this for you, batching included. Build batches by hand only when you are not on JavaScript.
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://api.genlook.app/storefront/v1/events" \
    -H "Authorization: Bearer pk_your_key_here" \
    -H "X-Genlook-Anonymous-Id: anon_550e8400-e29b-41d4-a716-446655440000" \
    -H "Content-Type: application/json" \
    -d '{
      "events": [
        {
          "event": "widget:button_click",
          "properties": { "product_id": "gid://shopify/Product/456" },
          "timestamp": "2024-01-15T10:30:00Z",
          "$insert_id": "event_123"
        }
      ],
      "context": {
        "anonymous_id": "anon_550e8400-e29b-41d4-a716-446655440000",
        "session_id": "sess_abc123",
        "$pageview_id": "pv_1",
        "widget_version": "wv_custom-1.0.0",
        "widget_enabled": true,
        "product_id": "gid://shopify/Product/456",
        "variant_id": null,
        "$screen_width": 390,
        "$screen_height": 844,
        "$timezone": "Europe/Paris",
        "$current_url": null,
        "$raw_user_agent": null,
        "integration": "sdk"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  await fetch("https://api.genlook.app/storefront/v1/events", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${PUBLISHABLE_KEY}`,
      "X-Genlook-Anonymous-Id": anonymousId,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      events: [
        {
          event: "widget:button_click",
          properties: { product_id: "gid://shopify/Product/456" },
          timestamp: new Date().toISOString(),
          $insert_id: `evt_${Date.now()}_${Math.random()}`,
        },
      ],
      context: {
        anonymous_id: anonymousId,           // "anon_...", same id as the header
        session_id: sessionId,               // "sess_...", mint once per session
        $pageview_id: pageviewId,
        widget_version: "wv_custom-1.0.0",   // must start with "wv_"
        widget_enabled: true,
        product_id: currentProductId,        // or null
        variant_id: null,
        $screen_width: screen?.width ?? 0,
        $screen_height: screen?.height ?? 0,
        $timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
        // Browser-only fields: send real values in a browser, null elsewhere.
        $current_url: typeof location !== "undefined" ? location.href : null,
        $raw_user_agent: typeof navigator !== "undefined" ? navigator.userAgent : null,
        integration: "sdk",
      },
    }),
  });
  ```
</RequestExample>

## Event Types

| Event                        | Description                |
| ---------------------------- | -------------------------- |
| `widget:button_click`        | Widget button clicked      |
| `widget:image_upload`        | User uploaded an image     |
| `widget:generation_start`    | Try-on started             |
| `widget:result_view`         | Result displayed           |
| `widget:share_clicked`       | Share button clicked       |
| `widget:download_clicked`    | Download button clicked    |
| `widget:add_to_cart_clicked` | Add to cart from widget    |
| `widget:add_to_cart`         | Add to cart succeeded      |
| `widget:email_collected`     | Email collected            |
| `product_page:page_view`     | Product page viewed        |
| `product_page:cart_add`      | Cart add from product page |

Event names follow `category:action` format with snake\_case. The names are part of the analytics contract, so send them exactly as listed.

## Identifying a signed-in shopper

Two optional headers attach the batch to a known shopper:

* `X-Genlook-Customer-Id`: platform customer id
* `X-Genlook-Customer-Email`: customer email

The request body is identical whether or not you send them.

## Authenticating

One endpoint serves every platform, and in most cases you send no credential at all: whatever sits between your code and Genlook adds one for you.

| Where your code runs                        | What you send                                                                              |
| ------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Your own app, headless storefront or server | `Authorization: Bearer pk_...`, as shown above                                             |
| Inside a Shopify or SHOPLINE storefront     | Nothing. The app proxy signs the request.                                                  |
| On a WooCommerce or PrestaShop site         | Nothing. Call your own site's Genlook proxy path and the plugin authenticates server-side. |

<Warning>
  Your store API key is a server credential. It never belongs in an app binary, a page bundle, or any request a browser makes. The only key meant to be embedded is the publishable one, and it always starts with `pk_`.
</Warning>

## Response

<ResponseField name="success" type="boolean" required>
  Whether the batch was processed.
</ResponseField>

<ResponseExample>
  ```json Success theme={null}
  {
    "success": true
  }
  ```
</ResponseExample>
