# Webhook events

> Every outgoing webhook event type with its payload schema and an example, generated from the OpenAPI document.

Source: https://shopassist.coec.dk/docs/webhooks/events · Updated 2026-10-01

Every event is delivered as a `POST` with the [envelope](/docs/webhooks#envelope) below the `data` key, signed as described in [verifying signatures](/docs/webhooks/verifying-signatures). Subscribe to event types per endpoint in **Developers → Webhooks**. New event types may be added at any time; ignore types you don't know.

| Event                                                     | Sent when                                                                                            |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [`action.executed`](#action-executed)                     | An order action the shopper confirmed (cancel, return, address change) was executed on the platform. |
| [`back_in_stock.notified`](#back-in-stock-notified)       | A back-in-stock alert was sent to the shopper.                                                       |
| [`back_in_stock.subscribed`](#back-in-stock-subscribed)   | A shopper subscribed to a back-in-stock alert.                                                       |
| [`conversation.closed`](#conversation-closed)             | A conversation was closed by the shopper, an agent or after the idle timeout.                        |
| [`conversation.created`](#conversation-created)           | A shopper started a conversation.                                                                    |
| [`csat.submitted`](#csat-submitted)                       | A shopper rated a conversation.                                                                      |
| [`handoff.requested`](#handoff-requested)                 | A conversation was handed off to your team; the payload carries up to 50 transcript messages.        |
| [`handoff.updated`](#handoff-updated)                     | A handoff changed state (taken over, delivered, resolved, failed).                                   |
| [`lead.captured`](#lead-captured)                         | A shopper left contact details in the chat.                                                          |
| [`lead.confirmed`](#lead-confirmed)                       | A shopper confirmed their newsletter sign-up (double opt-in).                                        |
| [`lead.unsubscribed`](#lead-unsubscribed)                 | A shopper unsubscribed.                                                                              |
| [`message.created`](#message-created)                     | A message was added to a conversation. Opt-in and high volume.                                       |
| [`order.attributed`](#order-attributed)                   | An order was attributed to a conversation.                                                           |
| [`privacy_request.completed`](#privacy-request-completed) | A privacy request (export or erasure) finished.                                                      |
| [`store.status_changed`](#store-status-changed)           | A store changed status (for example connected, disconnected, suspended).                             |
| [`sync.failed`](#sync-failed)                             | A catalog sync run failed.                                                                           |
| [`usage.threshold_reached`](#usage-threshold-reached)     | Usage reached 80 % or 100 % of the included conversations, or the overage budget.                    |

## `action.executed` [#action-executed]

An order action the shopper confirmed (cancel, return, address change) was executed on the platform.

Header `ShopAssist-Event-Type: action.executed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `ActionExecutedData` | yes | `{ action; conversation_id }`. |
| `data.action` | `ActionEventView` | yes | `{ id; action_type; mode; status; order_number; result; created_at; executed_at }`. |
| `data.action.action_type` | `"cancel_order" \| "cancellation_request" \| "return_order" \| "return_request"` | yes |  |
| `data.action.created_at` | `string` | yes | format date-time |
| `data.action.executed_at` | `string \| null` | yes | format date-time |
| `data.action.id` | `string` | yes | pattern `^act_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.action.mode` | `"direct" \| "request_only" \| "platform_self_service" \| "platform"` | yes |  |
| `data.action.order_number` | `string \| null` | yes |  |
| `data.action.result` | `object \| null` | yes |  |
| `data.action.status` | `string (enum)` | yes | one of proposed, executing, succeeded, requested, failed, declined, expired, superseded |
| `data.conversation_id` | `string` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"action.executed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "action": {
      "action_type": "cancel_order",
      "created_at": "2026-09-28T10:15:30.123Z",
      "executed_at": "2026-09-28T10:15:30.123Z",
      "id": "act_01j8000000e008000000000000",
      "mode": "direct",
      "order_number": "string",
      "result": {},
      "status": "proposed"
    },
    "conversation_id": "conv_01j8000000e008000000000000"
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "action.executed"
}
```

## `back_in_stock.notified` [#back-in-stock-notified]

A back-in-stock alert was sent to the shopper.

Header `ShopAssist-Event-Type: back_in_stock.notified`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `BackInStockEventData` | yes | `{ subscription: BackInStock }`. |
| `data.subscription` | `BackInStock` | yes | `BackInStock` (03 §6.19). |
| `data.subscription.created_at` | `string` | yes | format date-time |
| `data.subscription.email` | `string` | yes |  |
| `data.subscription.expires_at` | `string` | yes | format date-time |
| `data.subscription.id` | `string` | yes | pattern `^bis_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.subscription.locale` | `string` | yes |  |
| `data.subscription.location_id` | `string \| null` | yes | pattern `^loc_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.subscription.notified_at` | `string \| null` | yes | format date-time |
| `data.subscription.product_id` | `string` | yes | pattern `^prd_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.subscription.status` | `"pending_confirmation" \| "active" \| "notified" \| "cancelled" \| "expired"` | yes |  |
| `data.subscription.variant_id` | `string` | yes | pattern `^var_[0-9a-hjkmnp-tv-z]{26}$` |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"back_in_stock.notified"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "subscription": {
      "created_at": "2026-09-28T10:15:30.123Z",
      "email": "string",
      "expires_at": "2026-09-28T10:15:30.123Z",
      "id": "bis_01j8000000e008000000000000",
      "locale": "da-DK",
      "location_id": "loc_01j8000000e008000000000000",
      "notified_at": "2026-09-28T10:15:30.123Z",
      "product_id": "prd_01j8000000e008000000000000",
      "status": "pending_confirmation",
      "variant_id": "var_01j8000000e008000000000000"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "back_in_stock.notified"
}
```

## `back_in_stock.subscribed` [#back-in-stock-subscribed]

A shopper subscribed to a back-in-stock alert.

Header `ShopAssist-Event-Type: back_in_stock.subscribed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `BackInStockEventData` | yes | `{ subscription: BackInStock }`. |
| `data.subscription` | `BackInStock` | yes | `BackInStock` (03 §6.19). |
| `data.subscription.created_at` | `string` | yes | format date-time |
| `data.subscription.email` | `string` | yes |  |
| `data.subscription.expires_at` | `string` | yes | format date-time |
| `data.subscription.id` | `string` | yes | pattern `^bis_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.subscription.locale` | `string` | yes |  |
| `data.subscription.location_id` | `string \| null` | yes | pattern `^loc_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.subscription.notified_at` | `string \| null` | yes | format date-time |
| `data.subscription.product_id` | `string` | yes | pattern `^prd_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.subscription.status` | `"pending_confirmation" \| "active" \| "notified" \| "cancelled" \| "expired"` | yes |  |
| `data.subscription.variant_id` | `string` | yes | pattern `^var_[0-9a-hjkmnp-tv-z]{26}$` |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"back_in_stock.subscribed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "subscription": {
      "created_at": "2026-09-28T10:15:30.123Z",
      "email": "string",
      "expires_at": "2026-09-28T10:15:30.123Z",
      "id": "bis_01j8000000e008000000000000",
      "locale": "da-DK",
      "location_id": "loc_01j8000000e008000000000000",
      "notified_at": "2026-09-28T10:15:30.123Z",
      "product_id": "prd_01j8000000e008000000000000",
      "status": "pending_confirmation",
      "variant_id": "var_01j8000000e008000000000000"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "back_in_stock.subscribed"
}
```

## `conversation.closed` [#conversation-closed]

A conversation was closed by the shopper, an agent or after the idle timeout.

Header `ShopAssist-Event-Type: conversation.closed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `ConversationEventData` | yes | `{ conversation: ApiConversation }`. |
| `data.conversation` | `ApiConversation` | yes | `ConversationSummary` without `last_message_preview`, plus `platform_customer_id`. |
| `data.conversation.assigned_user_id` | `string \| null` | yes | pattern `^usr_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.channel` | `"widget" \| "playground" \| "preview" \| "demo" \| "api"` | yes |  |
| `data.conversation.closed_at` | `string \| null` | yes | format date-time |
| `data.conversation.csat_score` | `integer \| null` | yes |  |
| `data.conversation.handoff_requested_at` | `string \| null` | yes | format date-time |
| `data.conversation.handoff_state` | `"none" \| "requested" \| "live" \| "delivered_offline" \| "failed" \| "resolved"` | yes |  |
| `data.conversation.id` | `string` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.identity_level` | `"guest" \| "order_lookup" \| "order_otp" \| "customer"` | yes |  |
| `data.conversation.is_test` | `boolean` | yes |  |
| `data.conversation.last_message_at` | `string \| null` | yes | format date-time |
| `data.conversation.locale` | `string \| null` | yes |  |
| `data.conversation.message_count` | `integer` | yes | ≥ 0 |
| `data.conversation.mode` | `"ai" \| "human_requested" \| "human"` | yes |  |
| `data.conversation.platform_customer_id` | `string \| null` | yes | Only for identity_level = customer |
| `data.conversation.resolution` | `"resolved_ai" \| "handed_off" \| "abandoned" \| "unknown" \| null` | yes |  |
| `data.conversation.shopper` | `ShopperView` | yes | Shopper identity as shown on lists (masked for agent/viewer, 06 §5.3). |
| `data.conversation.shopper.email` | `string \| null` | yes |  |
| `data.conversation.shopper.is_masked` | `boolean` | yes |  |
| `data.conversation.shopper.is_verified` | `boolean` | yes |  |
| `data.conversation.shopper.name` | `string \| null` | yes |  |
| `data.conversation.started_at` | `string` | yes | format date-time |
| `data.conversation.status` | `"open" \| "closed"` | yes |  |
| `data.conversation.store_id` | `string` | yes | pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.tags` | `string[]` | yes |  |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"conversation.closed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "conversation": {
      "assigned_user_id": "usr_01j8000000e008000000000000",
      "channel": "widget",
      "closed_at": "2026-09-28T10:15:30.123Z",
      "csat_score": 1,
      "handoff_requested_at": "2026-09-28T10:15:30.123Z",
      "handoff_state": "none",
      "id": "conv_01j8000000e008000000000000",
      "identity_level": "guest",
      "is_test": true,
      "last_message_at": "2026-09-28T10:15:30.123Z",
      "locale": "da-DK",
      "message_count": 0,
      "mode": "ai",
      "platform_customer_id": "string",
      "resolution": "resolved_ai",
      "shopper": {
        "email": "string",
        "is_masked": true,
        "is_verified": true,
        "name": "string"
      },
      "started_at": "2026-09-28T10:15:30.123Z",
      "status": "open",
      "store_id": "sto_01j8000000e008000000000000",
      "tags": [
        "string"
      ]
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "conversation.closed"
}
```

## `conversation.created` [#conversation-created]

A shopper started a conversation.

Header `ShopAssist-Event-Type: conversation.created`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `ConversationEventData` | yes | `{ conversation: ApiConversation }`. |
| `data.conversation` | `ApiConversation` | yes | `ConversationSummary` without `last_message_preview`, plus `platform_customer_id`. |
| `data.conversation.assigned_user_id` | `string \| null` | yes | pattern `^usr_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.channel` | `"widget" \| "playground" \| "preview" \| "demo" \| "api"` | yes |  |
| `data.conversation.closed_at` | `string \| null` | yes | format date-time |
| `data.conversation.csat_score` | `integer \| null` | yes |  |
| `data.conversation.handoff_requested_at` | `string \| null` | yes | format date-time |
| `data.conversation.handoff_state` | `"none" \| "requested" \| "live" \| "delivered_offline" \| "failed" \| "resolved"` | yes |  |
| `data.conversation.id` | `string` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.identity_level` | `"guest" \| "order_lookup" \| "order_otp" \| "customer"` | yes |  |
| `data.conversation.is_test` | `boolean` | yes |  |
| `data.conversation.last_message_at` | `string \| null` | yes | format date-time |
| `data.conversation.locale` | `string \| null` | yes |  |
| `data.conversation.message_count` | `integer` | yes | ≥ 0 |
| `data.conversation.mode` | `"ai" \| "human_requested" \| "human"` | yes |  |
| `data.conversation.platform_customer_id` | `string \| null` | yes | Only for identity_level = customer |
| `data.conversation.resolution` | `"resolved_ai" \| "handed_off" \| "abandoned" \| "unknown" \| null` | yes |  |
| `data.conversation.shopper` | `ShopperView` | yes | Shopper identity as shown on lists (masked for agent/viewer, 06 §5.3). |
| `data.conversation.shopper.email` | `string \| null` | yes |  |
| `data.conversation.shopper.is_masked` | `boolean` | yes |  |
| `data.conversation.shopper.is_verified` | `boolean` | yes |  |
| `data.conversation.shopper.name` | `string \| null` | yes |  |
| `data.conversation.started_at` | `string` | yes | format date-time |
| `data.conversation.status` | `"open" \| "closed"` | yes |  |
| `data.conversation.store_id` | `string` | yes | pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.tags` | `string[]` | yes |  |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"conversation.created"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "conversation": {
      "assigned_user_id": "usr_01j8000000e008000000000000",
      "channel": "widget",
      "closed_at": "2026-09-28T10:15:30.123Z",
      "csat_score": 1,
      "handoff_requested_at": "2026-09-28T10:15:30.123Z",
      "handoff_state": "none",
      "id": "conv_01j8000000e008000000000000",
      "identity_level": "guest",
      "is_test": true,
      "last_message_at": "2026-09-28T10:15:30.123Z",
      "locale": "da-DK",
      "message_count": 0,
      "mode": "ai",
      "platform_customer_id": "string",
      "resolution": "resolved_ai",
      "shopper": {
        "email": "string",
        "is_masked": true,
        "is_verified": true,
        "name": "string"
      },
      "started_at": "2026-09-28T10:15:30.123Z",
      "status": "open",
      "store_id": "sto_01j8000000e008000000000000",
      "tags": [
        "string"
      ]
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "conversation.created"
}
```

## `csat.submitted` [#csat-submitted]

A shopper rated a conversation.

Header `ShopAssist-Event-Type: csat.submitted`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `CsatSubmittedData` | yes | `{ conversation_id; score; comment }`. |
| `data.comment` | `string \| null` | yes |  |
| `data.conversation_id` | `string` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.score` | `integer` | yes | ≥ 1 · ≤ 5 |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"csat.submitted"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "comment": "string",
    "conversation_id": "conv_01j8000000e008000000000000",
    "score": 1
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "csat.submitted"
}
```

## `handoff.requested` [#handoff-requested]

A conversation was handed off to your team; the payload carries up to 50 transcript messages.

Header `ShopAssist-Event-Type: handoff.requested`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `HandoffRequestedData` | yes | `{ handoff; conversation; transcript (≤ 50); dashboard_url }` (`handoff.shopper` carries `identity_level` and `email_verified`). |
| `data.conversation` | `ApiConversation` | yes | `ConversationSummary` without `last_message_preview`, plus `platform_customer_id`. |
| `data.conversation.assigned_user_id` | `string \| null` | yes | pattern `^usr_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.channel` | `"widget" \| "playground" \| "preview" \| "demo" \| "api"` | yes |  |
| `data.conversation.closed_at` | `string \| null` | yes | format date-time |
| `data.conversation.csat_score` | `integer \| null` | yes |  |
| `data.conversation.handoff_requested_at` | `string \| null` | yes | format date-time |
| `data.conversation.handoff_state` | `"none" \| "requested" \| "live" \| "delivered_offline" \| "failed" \| "resolved"` | yes |  |
| `data.conversation.id` | `string` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.identity_level` | `"guest" \| "order_lookup" \| "order_otp" \| "customer"` | yes |  |
| `data.conversation.is_test` | `boolean` | yes |  |
| `data.conversation.last_message_at` | `string \| null` | yes | format date-time |
| `data.conversation.locale` | `string \| null` | yes |  |
| `data.conversation.message_count` | `integer` | yes | ≥ 0 |
| `data.conversation.mode` | `"ai" \| "human_requested" \| "human"` | yes |  |
| `data.conversation.platform_customer_id` | `string \| null` | yes | Only for identity_level = customer |
| `data.conversation.resolution` | `"resolved_ai" \| "handed_off" \| "abandoned" \| "unknown" \| null` | yes |  |
| `data.conversation.shopper` | `ShopperView` | yes | Shopper identity as shown on lists (masked for agent/viewer, 06 §5.3). |
| `data.conversation.shopper.email` | `string \| null` | yes |  |
| `data.conversation.shopper.is_masked` | `boolean` | yes |  |
| `data.conversation.shopper.is_verified` | `boolean` | yes |  |
| `data.conversation.shopper.name` | `string \| null` | yes |  |
| `data.conversation.started_at` | `string` | yes | format date-time |
| `data.conversation.status` | `"open" \| "closed"` | yes |  |
| `data.conversation.store_id` | `string` | yes | pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.conversation.tags` | `string[]` | yes |  |
| `data.dashboard_url` | `string` | yes | format uri · ≤ 2048 characters |
| `data.handoff` | `HandoffAdmin` | yes | `HandoffAdmin = HandoffState & {…}` (03 §6). |
| `data.handoff.accepted_at` | `string \| null` | yes | format date-time |
| `data.handoff.agent` | `AgentRef \| null` | yes |  |
| `data.handoff.agent.display_name` | `string` | yes |  |
| `data.handoff.delivered_at` | `string \| null` | yes | format date-time |
| `data.handoff.expected_wait_seconds` | `integer \| null` | yes |  |
| `data.handoff.id` | `string` | yes | pattern `^hof_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.handoff.message` | `string` | yes | Localized shopper-facing status text |
| `data.handoff.reason` | `string (enum)` | yes | one of shopper_request, ai_escalation, action_request, policy, repeated_failure, error, outside_hours, quota_block, limit_reached |
| `data.handoff.requested_at` | `string` | yes | format date-time |
| `data.handoff.requested_by` | `"shopper" \| "ai" \| "agent" \| "system"` | yes |  |
| `data.handoff.shopper` | `HandoffShopper` | yes | `HandoffAdmin.shopper` (`platform_customer_id` only for `identity_level = customer`). |
| `data.handoff.shopper.email` | `string \| null` | yes |  |
| `data.handoff.shopper.email_verified` | `boolean` | yes |  |
| `data.handoff.shopper.identity_level` | `"guest" \| "order_lookup" \| "order_otp" \| "customer"` | yes |  |
| `data.handoff.shopper.name` | `string \| null` | yes |  |
| `data.handoff.shopper.platform_customer_id` | `string \| null` | yes |  |
| `data.handoff.state` | `"requested" \| "waiting_for_agent" \| "live" \| "delivered_offline" \| "failed" \| "resolved"` | yes |  |
| `data.handoff.status` | `string (enum)` | yes | one of requested, waiting_for_agent, live, delivering, delivered, failed, resolved, cancelled |
| `data.handoff.summary` | `string \| null` | yes |  |
| `data.handoff.target` | `"live_chat" \| "email" \| "helpdesk" \| null` | yes |  |
| `data.handoff.targets` | `HandoffTargetView[]` | yes |  |
| `data.handoff.targets[].external_url` | `string \| null` | yes | format uri · ≤ 2048 characters |
| `data.handoff.targets[].integration_id` | `string \| null` | yes | pattern `^int_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.handoff.targets[].last_error` | `string \| null` | yes |  |
| `data.handoff.targets[].status` | `"pending" \| "delivered" \| "failed" \| "skipped"` | yes |  |
| `data.handoff.targets[].type` | `string` | yes |  |
| `data.transcript` | `ApiMessage[]` | yes | ≤ 50 items |
| `data.transcript[].author` | `MessageAuthor` | yes | Who wrote a message. |
| `data.transcript[].author.display_name` | `string \| null` | yes |  |
| `data.transcript[].author.type` | `"shopper" \| "ai" \| "human" \| "system"` | yes |  |
| `data.transcript[].blocks` | `UiBlock[]` | yes |  |
| `data.transcript[].created_at` | `string` | yes | format date-time |
| `data.transcript[].id` | `string` | yes | pattern `^msg_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.transcript[].is_ai_generated` | `boolean` | yes |  |
| `data.transcript[].role` | `"shopper" \| "assistant" \| "agent" \| "system"` | yes |  |
| `data.transcript[].seq` | `integer` | yes | ≥ 0 |
| `data.transcript[].text` | `string` | yes |  |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"handoff.requested"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "conversation": {
      "assigned_user_id": "usr_01j8000000e008000000000000",
      "channel": "widget",
      "closed_at": "2026-09-28T10:15:30.123Z",
      "csat_score": 1,
      "handoff_requested_at": "2026-09-28T10:15:30.123Z",
      "handoff_state": "none",
      "id": "conv_01j8000000e008000000000000",
      "identity_level": "guest",
      "is_test": true,
      "last_message_at": "2026-09-28T10:15:30.123Z",
      "locale": "da-DK",
      "message_count": 0,
      "mode": "ai",
      "platform_customer_id": "string",
      "resolution": "resolved_ai",
      "shopper": {
        "email": "string",
        "is_masked": true,
        "is_verified": true,
        "name": "string"
      },
      "started_at": "2026-09-28T10:15:30.123Z",
      "status": "open",
      "store_id": "sto_01j8000000e008000000000000",
      "tags": [
        "string"
      ]
    },
    "dashboard_url": "https://shop.example/products/linen-shirt",
    "handoff": {
      "agent": null,
      "expected_wait_seconds": 120,
      "id": "hof_01j8000000e008000000000001",
      "message": "We've let the team know. Someone will be with you shortly.",
      "state": "waiting_for_agent",
      "target": "live_chat"
    },
    "transcript": [
      {
        "author": {
          "display_name": "string",
          "type": "shopper"
        },
        "blocks": [
          {
            "as_of": "2026-09-28T10:15:30.123Z",
            "id": "b1",
            "items": [
              {
                "actions": {
                  "add_to_cart": true,
                  "check_store_stock": true,
                  "notify_back_in_stock": false
                },
                "as_of": "2026-09-28T10:15:30.123Z",
                "availability": {
                  "as_of": "2026-09-28T10:15:30.123Z",
                  "label": "Only 2 left",
                  "quantity": 2,
                  "status": "low_stock"
                },
                "badges": [
                  "New"
                ],
                "default_variant_id": "var_01j8000000e008000000000001",
                "image": {
                  "alt": "City bike",
                  "height": 600,
                  "url": "https://cdn.shopify.com/s/files/1/demo/bike-1.jpg",
                  "width": 800
                },
                "options": [
                  {
                    "name": "Size",
                    "values": [
                      {
                        "is_available": true,
                        "value": "M"
                      },
                      {
                        "is_available": false,
                        "value": "L"
                      }
                    ]
                  }
                ],
                "price": {
                  "amount": "4999",
                  "currency": "DKK",
                  "display": "4.999,00 kr."
                },
                "product_id": "prd_01j8000000e008000000000001",
                "rating": {
                  "average": 4.6,
                  "count": 128
                },
                "subtitle": "Nordic Bikes",
                "title": "Nordic City Bike 7-speed",
                "url": "https://nordicbikes.example/products/city-bike?utm_source=shopassist&utm_medium=chat",
                "variants": [
                  {
                    "availability": {
                      "as_of": "2026-09-28T10:15:30.123Z",
                      "label": "Only 2 left",
                      "quantity": 2,
                      "status": "low_stock"
                    },
                    "cart_ref": {
                      "variant_gid": "gid://shopify/ProductVariant/4711"
                    },
                    "options": {
                      "Size": "M"
                    },
                    "price": {
                      "amount": "4999",
                      "currency": "DKK",
                      "display": "4.999,00 kr."
                    },
                    "title": "M",
                    "variant_id": "var_01j8000000e008000000000001"
                  }
                ]
              }
            ],
            "layout": "carousel",
            "title": "City bikes in stock",
            "type": "products"
          }
        ],
        "created_at": "2026-09-28T10:15:30.123Z",
        "id": "msg_01j8000000e008000000000000",
        "is_ai_generated": true,
        "role": "shopper",
        "seq": 0,
        "text": "string"
      }
    ]
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "handoff.requested"
}
```

## `handoff.updated` [#handoff-updated]

A handoff changed state (taken over, delivered, resolved, failed).

Header `ShopAssist-Event-Type: handoff.updated`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `HandoffUpdatedData` | yes | `{ handoff: HandoffAdmin }`. |
| `data.handoff` | `HandoffAdmin` | yes | `HandoffAdmin = HandoffState & {…}` (03 §6). |
| `data.handoff.accepted_at` | `string \| null` | yes | format date-time |
| `data.handoff.agent` | `AgentRef \| null` | yes |  |
| `data.handoff.agent.display_name` | `string` | yes |  |
| `data.handoff.delivered_at` | `string \| null` | yes | format date-time |
| `data.handoff.expected_wait_seconds` | `integer \| null` | yes |  |
| `data.handoff.id` | `string` | yes | pattern `^hof_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.handoff.message` | `string` | yes | Localized shopper-facing status text |
| `data.handoff.reason` | `string (enum)` | yes | one of shopper_request, ai_escalation, action_request, policy, repeated_failure, error, outside_hours, quota_block, limit_reached |
| `data.handoff.requested_at` | `string` | yes | format date-time |
| `data.handoff.requested_by` | `"shopper" \| "ai" \| "agent" \| "system"` | yes |  |
| `data.handoff.shopper` | `HandoffShopper` | yes | `HandoffAdmin.shopper` (`platform_customer_id` only for `identity_level = customer`). |
| `data.handoff.shopper.email` | `string \| null` | yes |  |
| `data.handoff.shopper.email_verified` | `boolean` | yes |  |
| `data.handoff.shopper.identity_level` | `"guest" \| "order_lookup" \| "order_otp" \| "customer"` | yes |  |
| `data.handoff.shopper.name` | `string \| null` | yes |  |
| `data.handoff.shopper.platform_customer_id` | `string \| null` | yes |  |
| `data.handoff.state` | `"requested" \| "waiting_for_agent" \| "live" \| "delivered_offline" \| "failed" \| "resolved"` | yes |  |
| `data.handoff.status` | `string (enum)` | yes | one of requested, waiting_for_agent, live, delivering, delivered, failed, resolved, cancelled |
| `data.handoff.summary` | `string \| null` | yes |  |
| `data.handoff.target` | `"live_chat" \| "email" \| "helpdesk" \| null` | yes |  |
| `data.handoff.targets` | `HandoffTargetView[]` | yes |  |
| `data.handoff.targets[].external_url` | `string \| null` | yes | format uri · ≤ 2048 characters |
| `data.handoff.targets[].integration_id` | `string \| null` | yes | pattern `^int_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.handoff.targets[].last_error` | `string \| null` | yes |  |
| `data.handoff.targets[].status` | `"pending" \| "delivered" \| "failed" \| "skipped"` | yes |  |
| `data.handoff.targets[].type` | `string` | yes |  |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"handoff.updated"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "handoff": {
      "agent": null,
      "expected_wait_seconds": 120,
      "id": "hof_01j8000000e008000000000001",
      "message": "We've let the team know. Someone will be with you shortly.",
      "state": "waiting_for_agent",
      "target": "live_chat"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "handoff.updated"
}
```

## `lead.captured` [#lead-captured]

A shopper left contact details in the chat.

Header `ShopAssist-Event-Type: lead.captured`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `LeadEventData` | yes | `{ lead: Lead }`. |
| `data.lead` | `Lead` | yes | `Lead` (03 §6.19). |
| `data.lead.confirmed_at` | `string \| null` | yes | format date-time |
| `data.lead.consents` | `ConsentRecordView[]` | yes |  |
| `data.lead.consents[].confirmed_at` | `string \| null` | yes | format date-time |
| `data.lead.consents[].consent_text` | `string` | yes |  |
| `data.lead.consents[].consent_text_version` | `string` | yes |  |
| `data.lead.consents[].created_at` | `string` | yes | format date-time |
| `data.lead.consents[].id` | `string` | yes | pattern `^cns_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.consents[].is_granted` | `boolean` | yes |  |
| `data.lead.consents[].locale` | `string` | yes |  |
| `data.lead.consents[].purpose` | `string (enum)` | yes | one of newsletter, marketing_email, back_in_stock, transcript_email, handoff_share, analytics, preferences |
| `data.lead.consents[].source` | `string (enum)` | yes | one of widget_form, chat, cmp, double_opt_in, unsubscribe, dashboard, api |
| `data.lead.consents[].withdrawn_at` | `string \| null` | yes | format date-time |
| `data.lead.conversation_id` | `string \| null` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.created_at` | `string` | yes | format date-time |
| `data.lead.email` | `string` | yes |  |
| `data.lead.id` | `string` | yes | pattern `^lead_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.locale` | `string \| null` | yes |  |
| `data.lead.name` | `string \| null` | yes |  |
| `data.lead.newsletter_status` | `"none" \| "pending_confirmation" \| "confirmed" \| "unsubscribed"` | yes |  |
| `data.lead.platform_sync_status` | `"not_required" \| "pending" \| "synced" \| "failed"` | yes |  |
| `data.lead.purposes` | `("newsletter" \| "contact_request" \| "back_in_stock" \| "transcript")[]` | yes |  |
| `data.lead.source` | `"lead_form" \| "chat_tool" \| "handoff" \| "back_in_stock"` | yes |  |
| `data.lead.unsubscribed_at` | `string \| null` | yes | format date-time |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"lead.captured"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "lead": {
      "confirmed_at": "2026-09-28T10:15:30.123Z",
      "consents": [
        {
          "confirmed_at": "2026-09-28T10:15:30.123Z",
          "consent_text": "string",
          "consent_text_version": "string",
          "created_at": "2026-09-28T10:15:30.123Z",
          "id": "cns_01j8000000e008000000000000",
          "is_granted": true,
          "locale": "da-DK",
          "purpose": "newsletter",
          "source": "widget_form",
          "withdrawn_at": "2026-09-28T10:15:30.123Z"
        }
      ],
      "conversation_id": "conv_01j8000000e008000000000000",
      "created_at": "2026-09-28T10:15:30.123Z",
      "email": "string",
      "id": "lead_01j8000000e008000000000000",
      "locale": "da-DK",
      "name": "string",
      "newsletter_status": "none",
      "platform_sync_status": "not_required",
      "purposes": [
        "newsletter"
      ],
      "source": "lead_form",
      "unsubscribed_at": "2026-09-28T10:15:30.123Z"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "lead.captured"
}
```

## `lead.confirmed` [#lead-confirmed]

A shopper confirmed their newsletter sign-up (double opt-in).

Header `ShopAssist-Event-Type: lead.confirmed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `LeadEventData` | yes | `{ lead: Lead }`. |
| `data.lead` | `Lead` | yes | `Lead` (03 §6.19). |
| `data.lead.confirmed_at` | `string \| null` | yes | format date-time |
| `data.lead.consents` | `ConsentRecordView[]` | yes |  |
| `data.lead.consents[].confirmed_at` | `string \| null` | yes | format date-time |
| `data.lead.consents[].consent_text` | `string` | yes |  |
| `data.lead.consents[].consent_text_version` | `string` | yes |  |
| `data.lead.consents[].created_at` | `string` | yes | format date-time |
| `data.lead.consents[].id` | `string` | yes | pattern `^cns_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.consents[].is_granted` | `boolean` | yes |  |
| `data.lead.consents[].locale` | `string` | yes |  |
| `data.lead.consents[].purpose` | `string (enum)` | yes | one of newsletter, marketing_email, back_in_stock, transcript_email, handoff_share, analytics, preferences |
| `data.lead.consents[].source` | `string (enum)` | yes | one of widget_form, chat, cmp, double_opt_in, unsubscribe, dashboard, api |
| `data.lead.consents[].withdrawn_at` | `string \| null` | yes | format date-time |
| `data.lead.conversation_id` | `string \| null` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.created_at` | `string` | yes | format date-time |
| `data.lead.email` | `string` | yes |  |
| `data.lead.id` | `string` | yes | pattern `^lead_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.locale` | `string \| null` | yes |  |
| `data.lead.name` | `string \| null` | yes |  |
| `data.lead.newsletter_status` | `"none" \| "pending_confirmation" \| "confirmed" \| "unsubscribed"` | yes |  |
| `data.lead.platform_sync_status` | `"not_required" \| "pending" \| "synced" \| "failed"` | yes |  |
| `data.lead.purposes` | `("newsletter" \| "contact_request" \| "back_in_stock" \| "transcript")[]` | yes |  |
| `data.lead.source` | `"lead_form" \| "chat_tool" \| "handoff" \| "back_in_stock"` | yes |  |
| `data.lead.unsubscribed_at` | `string \| null` | yes | format date-time |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"lead.confirmed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "lead": {
      "confirmed_at": "2026-09-28T10:15:30.123Z",
      "consents": [
        {
          "confirmed_at": "2026-09-28T10:15:30.123Z",
          "consent_text": "string",
          "consent_text_version": "string",
          "created_at": "2026-09-28T10:15:30.123Z",
          "id": "cns_01j8000000e008000000000000",
          "is_granted": true,
          "locale": "da-DK",
          "purpose": "newsletter",
          "source": "widget_form",
          "withdrawn_at": "2026-09-28T10:15:30.123Z"
        }
      ],
      "conversation_id": "conv_01j8000000e008000000000000",
      "created_at": "2026-09-28T10:15:30.123Z",
      "email": "string",
      "id": "lead_01j8000000e008000000000000",
      "locale": "da-DK",
      "name": "string",
      "newsletter_status": "none",
      "platform_sync_status": "not_required",
      "purposes": [
        "newsletter"
      ],
      "source": "lead_form",
      "unsubscribed_at": "2026-09-28T10:15:30.123Z"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "lead.confirmed"
}
```

## `lead.unsubscribed` [#lead-unsubscribed]

A shopper unsubscribed.

Header `ShopAssist-Event-Type: lead.unsubscribed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `LeadEventData` | yes | `{ lead: Lead }`. |
| `data.lead` | `Lead` | yes | `Lead` (03 §6.19). |
| `data.lead.confirmed_at` | `string \| null` | yes | format date-time |
| `data.lead.consents` | `ConsentRecordView[]` | yes |  |
| `data.lead.consents[].confirmed_at` | `string \| null` | yes | format date-time |
| `data.lead.consents[].consent_text` | `string` | yes |  |
| `data.lead.consents[].consent_text_version` | `string` | yes |  |
| `data.lead.consents[].created_at` | `string` | yes | format date-time |
| `data.lead.consents[].id` | `string` | yes | pattern `^cns_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.consents[].is_granted` | `boolean` | yes |  |
| `data.lead.consents[].locale` | `string` | yes |  |
| `data.lead.consents[].purpose` | `string (enum)` | yes | one of newsletter, marketing_email, back_in_stock, transcript_email, handoff_share, analytics, preferences |
| `data.lead.consents[].source` | `string (enum)` | yes | one of widget_form, chat, cmp, double_opt_in, unsubscribe, dashboard, api |
| `data.lead.consents[].withdrawn_at` | `string \| null` | yes | format date-time |
| `data.lead.conversation_id` | `string \| null` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.created_at` | `string` | yes | format date-time |
| `data.lead.email` | `string` | yes |  |
| `data.lead.id` | `string` | yes | pattern `^lead_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.lead.locale` | `string \| null` | yes |  |
| `data.lead.name` | `string \| null` | yes |  |
| `data.lead.newsletter_status` | `"none" \| "pending_confirmation" \| "confirmed" \| "unsubscribed"` | yes |  |
| `data.lead.platform_sync_status` | `"not_required" \| "pending" \| "synced" \| "failed"` | yes |  |
| `data.lead.purposes` | `("newsletter" \| "contact_request" \| "back_in_stock" \| "transcript")[]` | yes |  |
| `data.lead.source` | `"lead_form" \| "chat_tool" \| "handoff" \| "back_in_stock"` | yes |  |
| `data.lead.unsubscribed_at` | `string \| null` | yes | format date-time |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"lead.unsubscribed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "lead": {
      "confirmed_at": "2026-09-28T10:15:30.123Z",
      "consents": [
        {
          "confirmed_at": "2026-09-28T10:15:30.123Z",
          "consent_text": "string",
          "consent_text_version": "string",
          "created_at": "2026-09-28T10:15:30.123Z",
          "id": "cns_01j8000000e008000000000000",
          "is_granted": true,
          "locale": "da-DK",
          "purpose": "newsletter",
          "source": "widget_form",
          "withdrawn_at": "2026-09-28T10:15:30.123Z"
        }
      ],
      "conversation_id": "conv_01j8000000e008000000000000",
      "created_at": "2026-09-28T10:15:30.123Z",
      "email": "string",
      "id": "lead_01j8000000e008000000000000",
      "locale": "da-DK",
      "name": "string",
      "newsletter_status": "none",
      "platform_sync_status": "not_required",
      "purposes": [
        "newsletter"
      ],
      "source": "lead_form",
      "unsubscribed_at": "2026-09-28T10:15:30.123Z"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "lead.unsubscribed"
}
```

## `message.created` [#message-created]

A message was added to a conversation. Opt-in and high volume.

Header `ShopAssist-Event-Type: message.created`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `MessageCreatedData` | yes | `{ conversation_id; message: ApiMessage }` (opt-in, high volume). |
| `data.conversation_id` | `string` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.message` | `ApiMessage` | yes | `{ id; seq; role; text; blocks; is_ai_generated; author: {type; display_name}; created_at }`. |
| `data.message.author` | `MessageAuthor` | yes | Who wrote a message. |
| `data.message.author.display_name` | `string \| null` | yes |  |
| `data.message.author.type` | `"shopper" \| "ai" \| "human" \| "system"` | yes |  |
| `data.message.blocks` | `UiBlock[]` | yes |  |
| `data.message.created_at` | `string` | yes | format date-time |
| `data.message.id` | `string` | yes | pattern `^msg_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.message.is_ai_generated` | `boolean` | yes |  |
| `data.message.role` | `"shopper" \| "assistant" \| "agent" \| "system"` | yes |  |
| `data.message.seq` | `integer` | yes | ≥ 0 |
| `data.message.text` | `string` | yes |  |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"message.created"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "conversation_id": "conv_01j8000000e008000000000000",
    "message": {
      "author": {
        "display_name": "string",
        "type": "shopper"
      },
      "blocks": [
        {
          "as_of": "2026-09-28T10:15:30.123Z",
          "id": "b1",
          "items": [
            {
              "actions": {
                "add_to_cart": true,
                "check_store_stock": true,
                "notify_back_in_stock": false
              },
              "as_of": "2026-09-28T10:15:30.123Z",
              "availability": {
                "as_of": "2026-09-28T10:15:30.123Z",
                "label": "Only 2 left",
                "quantity": 2,
                "status": "low_stock"
              },
              "badges": [
                "New"
              ],
              "default_variant_id": "var_01j8000000e008000000000001",
              "image": {
                "alt": "City bike",
                "height": 600,
                "url": "https://cdn.shopify.com/s/files/1/demo/bike-1.jpg",
                "width": 800
              },
              "options": [
                {
                  "name": "Size",
                  "values": [
                    {
                      "is_available": true,
                      "value": "M"
                    },
                    {
                      "is_available": false,
                      "value": "L"
                    }
                  ]
                }
              ],
              "price": {
                "amount": "4999",
                "currency": "DKK",
                "display": "4.999,00 kr."
              },
              "product_id": "prd_01j8000000e008000000000001",
              "rating": {
                "average": 4.6,
                "count": 128
              },
              "subtitle": "Nordic Bikes",
              "title": "Nordic City Bike 7-speed",
              "url": "https://nordicbikes.example/products/city-bike?utm_source=shopassist&utm_medium=chat",
              "variants": [
                {
                  "availability": {
                    "as_of": "2026-09-28T10:15:30.123Z",
                    "label": "Only 2 left",
                    "quantity": 2,
                    "status": "low_stock"
                  },
                  "cart_ref": {
                    "variant_gid": "gid://shopify/ProductVariant/4711"
                  },
                  "options": {
                    "Size": "M"
                  },
                  "price": {
                    "amount": "4999",
                    "currency": "DKK",
                    "display": "4.999,00 kr."
                  },
                  "title": "M",
                  "variant_id": "var_01j8000000e008000000000001"
                }
              ]
            }
          ],
          "layout": "carousel",
          "title": "City bikes in stock",
          "type": "products"
        }
      ],
      "created_at": "2026-09-28T10:15:30.123Z",
      "id": "msg_01j8000000e008000000000000",
      "is_ai_generated": true,
      "role": "shopper",
      "seq": 0,
      "text": "string"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "message.created"
}
```

## `order.attributed` [#order-attributed]

An order was attributed to a conversation.

Header `ShopAssist-Event-Type: order.attributed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `OrderAttributedData` | yes | `{ order }`. |
| `data.order` | `AttributedOrderEventView` | yes | `{ id; platform_order_id; order_number; attribution_type; total; assisted_amount; ordered_at; conversation_id }`. |
| `data.order.assisted_amount` | `Money \| null` | yes |  |
| `data.order.assisted_amount.amount` | `string` | yes | pattern `^-?\d+(\.\d{1,6})?$` |
| `data.order.assisted_amount.currency` | `string` | yes | pattern `^[A-Z]{3}$` |
| `data.order.attribution_type` | `"bot_add_to_cart" \| "bot_checkout_link" \| "assisted_session"` | yes |  |
| `data.order.conversation_id` | `string \| null` | yes | pattern `^conv_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.order.id` | `string` | yes | pattern `^aor_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.order.order_number` | `string \| null` | yes |  |
| `data.order.ordered_at` | `string` | yes | format date-time |
| `data.order.platform_order_id` | `string` | yes |  |
| `data.order.total` | `Money` | yes | An amount with its currency (`03` §2.4 `Money`). |
| `data.order.total.amount` | `string` | yes | pattern `^-?\d+(\.\d{1,6})?$` |
| `data.order.total.currency` | `string` | yes | pattern `^[A-Z]{3}$` |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"order.attributed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "order": {
      "assisted_amount": {
        "amount": "499.95",
        "currency": "DKK"
      },
      "attribution_type": "bot_add_to_cart",
      "conversation_id": "conv_01j8000000e008000000000000",
      "id": "aor_01j8000000e008000000000000",
      "order_number": "string",
      "ordered_at": "2026-09-28T10:15:30.123Z",
      "platform_order_id": "string",
      "total": {
        "amount": "499.95",
        "currency": "DKK"
      }
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "order.attributed"
}
```

## `privacy_request.completed` [#privacy-request-completed]

A privacy request (export or erasure) finished.

Header `ShopAssist-Event-Type: privacy_request.completed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `PrivacyRequestCompletedData` | yes | `{ privacy_request: PrivacyRequest }`. |
| `data.privacy_request` | `PrivacyRequest` | yes | `PrivacyRequest` (03 §6.16). |
| `data.privacy_request.completed_at` | `string \| null` | yes | format date-time |
| `data.privacy_request.created_at` | `string` | yes | format date-time |
| `data.privacy_request.due_at` | `string` | yes | format date-time |
| `data.privacy_request.id` | `string` | yes | pattern `^prq_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.privacy_request.source` | `string (enum)` | yes | one of dashboard, merchant_api, shopify_customers_data_request, shopify_customers_redact, shopify_shop_redact, woocommerce, prestashop, magento2, operator |
| `data.privacy_request.stats` | `object` | yes |  |
| `data.privacy_request.status` | `"received" \| "processing" \| "completed" \| "failed" \| "cancelled"` | yes |  |
| `data.privacy_request.subject_summary` | `string` | yes | Masked, e.g. "email j***@example.com", "customer 4711" |
| `data.privacy_request.type` | `"export" \| "delete"` | yes |  |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"privacy_request.completed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "privacy_request": {
      "completed_at": "2026-09-28T10:15:30.123Z",
      "created_at": "2026-09-28T10:15:30.123Z",
      "due_at": "2026-09-28T10:15:30.123Z",
      "id": "prq_01j8000000e008000000000000",
      "source": "dashboard",
      "stats": {},
      "status": "received",
      "subject_summary": "string",
      "type": "export"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "privacy_request.completed"
}
```

## `store.status_changed` [#store-status-changed]

A store changed status (for example connected, disconnected, suspended).

Header `ShopAssist-Event-Type: store.status_changed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `StoreStatusChangedData` | yes | `{ store: StorePublic; previous_status }`. |
| `data.previous_status` | `string (enum)` | yes | one of pending_connect, connecting, syncing, active, paused, reconnect_required, disconnected, uninstalled, suspended |
| `data.store` | `StorePublic` | yes | `{ id; name; platform; status; primary_domain; catalog_locale; currency; timezone; capabilities; created_at }`. |
| `data.store.capabilities` | `object` | yes | stores.capabilities (02 §17) |
| `data.store.catalog_locale` | `string` | yes | 2–35 characters · pattern `^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$` |
| `data.store.created_at` | `string` | yes | format date-time |
| `data.store.currency` | `string` | yes | pattern `^[A-Z]{3}$` |
| `data.store.id` | `string` | yes | pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.store.name` | `string` | yes |  |
| `data.store.platform` | `"shopify" \| "magento2" \| "woocommerce" \| "prestashop" \| "generic"` | yes |  |
| `data.store.primary_domain` | `string \| null` | yes |  |
| `data.store.status` | `string (enum)` | yes | one of pending_connect, connecting, syncing, active, paused, reconnect_required, disconnected, uninstalled, suspended |
| `data.store.timezone` | `string` | yes |  |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"store.status_changed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "previous_status": "pending_connect",
    "store": {
      "capabilities": {},
      "catalog_locale": "da-DK",
      "created_at": "2026-09-28T10:15:30.123Z",
      "currency": "DKK",
      "id": "sto_01j8000000e008000000000000",
      "name": "string",
      "platform": "shopify",
      "primary_domain": "string",
      "status": "pending_connect",
      "timezone": "string"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "store.status_changed"
}
```

## `sync.failed` [#sync-failed]

A catalog sync run failed.

Header `ShopAssist-Event-Type: sync.failed`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `SyncFailedData` | yes | `{ sync_run: SyncRun }` (topic `sync.run_failed`). |
| `data.sync_run` | `SyncRun` | yes | `SyncRun` (03 §6.9). |
| `data.sync_run.created_at` | `string` | yes | format date-time |
| `data.sync_run.error_code` | `string \| null` | yes |  |
| `data.sync_run.error_detail` | `string \| null` | yes | Merchant-safe message |
| `data.sync_run.finished_at` | `string \| null` | yes | format date-time |
| `data.sync_run.id` | `string` | yes | pattern `^sync_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.sync_run.kind` | `string (enum)` | yes | one of initial, incremental, reconcile, content, translations, inventory, locations, single, feed |
| `data.sync_run.progress` | `object` | yes | {phase, done, total} |
| `data.sync_run.started_at` | `string \| null` | yes | format date-time |
| `data.sync_run.stats` | `object` | yes |  |
| `data.sync_run.status` | `"queued" \| "running" \| "succeeded" \| "partial" \| "failed" \| "cancelled"` | yes |  |
| `data.sync_run.trigger` | `string (enum)` | yes | one of install, webhook, push, ping, schedule, manual, operator |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"sync.failed"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "sync_run": {
      "created_at": "2026-09-28T10:15:30.123Z",
      "error_code": "string",
      "error_detail": "string",
      "finished_at": "2026-09-28T10:15:30.123Z",
      "id": "sync_01j8000000e008000000000000",
      "kind": "initial",
      "progress": {},
      "started_at": "2026-09-28T10:15:30.123Z",
      "stats": {},
      "status": "queued",
      "trigger": "install"
    }
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "sync.failed"
}
```

## `usage.threshold_reached` [#usage-threshold-reached]

Usage reached 80 % or 100 % of the included conversations, or the overage budget.

Header `ShopAssist-Event-Type: usage.threshold_reached`. Acknowledge with any 2xx within 10 seconds.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `api_version` | `"v1"` | yes | default "v1" |
| `created_at` | `string` | yes | format date-time |
| `data` | `UsageThresholdReachedData` | yes | `{ billing_account_id; threshold; units_total; included_units }` (mapping from the internal thresholds: 01 §8.1). |
| `data.billing_account_id` | `string` | yes | pattern `^bac_[0-9a-hjkmnp-tv-z]{26}$` |
| `data.included_units` | `integer \| null` | yes | ≥ 0 |
| `data.threshold` | `"80_percent" \| "100_percent" \| "budget_reached"` | yes |  |
| `data.units_total` | `integer` | yes | ≥ 0 |
| `id` | `string` | yes | pattern `^evt_[0-9a-hjkmnp-tv-z]{26}$` |
| `organization_id` | `string` | yes | pattern `^org_[0-9a-hjkmnp-tv-z]{26}$` |
| `store_id` | `string \| null` | yes | null for organization-level events (usage) · pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$` |
| `type` | `"usage.threshold_reached"` | yes |  |

```json
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "billing_account_id": "bac_01j8000000e008000000000000",
    "included_units": 0,
    "threshold": "80_percent",
    "units_total": 0
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "usage.threshold_reached"
}
```
