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

# Product webhooks

> Catalog lifecycle, delist, price, and inventory event payloads.

Product webhooks describe changes to the OmniCommerce catalog resource. They
are emitted by product and developer workflows; they are not pass-through
copies of marketplace listing notifications.

Subscribe with `POST /api/v1/webhooks`. See [Webhooks](https://docs.omnicommerce.sg/webhooks/overview.md) for
signature verification, delivery behavior, and retries.

To create these changes through the developer API, see
[Catalog price, cost, and inventory updates](https://docs.omnicommerce.sg/guides/catalog-updates.md).

> [!WARNING]
>
> Product events currently use the original event envelope shown below, not the
> versioned order envelope. Use `eventId` and `eventType`; do not expect
> `schemaVersion`, `subject`, or `resourceVersion` on product events.

## Events

| Event | Semantics |
| - | - |
| `product.created` | First catalog snapshot after creation |
| `product.updated` | Full snapshot plus all changed public field paths |
| `product.deleted` | Final available snapshot; `changedFields` normally includes status |
| `product.delisted` | Listing removed from named marketplaces; the catalog product stays |
| `product.price.changed` | Full snapshot plus changed pricing field paths |
| `product.inventory.changed` | Full snapshot with `changedFields=["inventory.quantity"]` |

When a price or inventory mutation also changes the product, OmniCommerce can
emit both `product.updated` and the specific event. Subscribe only to the
specific events if that is all your integration needs.

## Envelope

| Field | Type | Meaning |
| - | - | - |
| `eventId` | string | Stable recipient idempotency key |
| `eventType` | product event key | Canonical subscription event |
| `organizationId` | string | Tenant boundary |
| `productId` | string | Stable OmniCommerce product ID |
| `sku` | string or `null` | Product SKU when present |
| `changedFields` | string array | Public product paths changed by this mutation |
| `occurredAt` | ISO timestamp | Time the product event occurred |
| `source` | string, optional | Internal workflow provenance |
| `data.product` | object | Complete product webhook snapshot |

Common changed paths include `name`, `description`, `sku`, `brand`, `status`,
`price`, `salePrice`, `compareAtPrice`, `costPrice`, `mapPrice`, `msrpPrice`,
`currency`, `inventory.quantity`, `tags`, `promotions`, `variants`, and
`variationSchema`.

## Product snapshot

| Field | Type | Description |
| - | - | - |
| `id` | string | Stable OmniCommerce product ID |
| `parentId` | string or `null` | Parent product ID for a variant |
| `type` | string | `product` or `variant` |
| `productFamilyId` | string | Stable product-family grouping ID |
| `productFamilyName` | string or `null` | Human-facing product-family name |
| `name` | string or `null` | Catalog product name |
| `description` | string or `null` | Catalog description |
| `sku` | string or `null` | Primary SKU |
| `brand` | string or `null` | Brand |
| `status` | string or `null` | Current catalog status |
| `price` | number or `null` | Catalog price |
| `salePrice` | number or `null` | Optional sale price |
| `compareAtPrice` | number or `null` | Compare-at/reference price |
| `costPrice` | number or `null` | Catalog unit-cost input (source amount) |
| `costPriceCurrency` | string or `null` | Currency of catalog cost |
| `mapPrice` | number or `null` | Minimum advertised price |
| `msrpPrice` | number or `null` | Manufacturer suggested retail price |
| `currency` | string or `null` | Currency for price values |
| `upc` | string or `null` | Normalized UPC/GTIN when available |
| `category` | string or `null` | Catalog category |
| `tags` | string array | Catalog tags, including product-family tags |
| `inventory` | object | Inventory data, including `quantity` when known |
| `images` | array | Product images |
| `variants` | array | Variant snapshots |
| `variationSchema` | object | Catalog variation definition |
| `marketplaces` | object | Connected marketplace listing metadata |
| `promotions` | array | Current materialized promotion context |
| `createdAt` | timestamp or `null` | OmniCommerce creation time |
| `updatedAt` | timestamp or `null` | Latest OmniCommerce update time |

Product price values are JSON numbers in this existing contract. Always pair a
price with `currency`; do not combine amounts across currencies. The versioned
order contract uses decimal strings for monetary precision.

## Example price change

```json theme={null}
{
  "eventId": "evt_61ec5106afc74e0cab8386df8b3980ea",
  "eventType": "product.price.changed",
  "organizationId": "org_123",
  "productId": "prod_456",
  "sku": "LAMP-1",
  "changedFields": ["price", "salePrice"],
  "occurredAt": "2026-08-29T03:10:00.000Z",
  "source": "product.quick-update",
  "data": {
    "product": {
      "id": "prod_456",
      "parentId": null,
      "type": "product",
      "productFamilyId": "prod_456",
      "productFamilyName": "Desk lamps",
      "name": "Desk lamp",
      "description": "Warm adjustable desk lamp",
      "sku": "LAMP-1",
      "brand": "Omni",
      "status": "active",
      "price": 19.95,
      "salePrice": 14.95,
      "compareAtPrice": 24.95,
      "costPrice": 8.5,
      "costPriceCurrency": "SGD",
      "mapPrice": 14,
      "msrpPrice": 24.95,
      "currency": "SGD",
      "upc": "012345678905",
      "category": "Lighting",
      "tags": ["Desk_Lamps"],
      "inventory": { "quantity": 7 },
      "images": [{ "url": "https://cdn.example.com/lamp.jpg" }],
      "variants": [],
      "variationSchema": {},
      "marketplaces": {},
      "promotions": [],
      "createdAt": "2026-08-20T02:00:00.000Z",
      "updatedAt": "2026-08-29T03:10:00.000Z"
    }
  }
}
```

## Consumer rules

* Deduplicate by `eventId` or `X-Omni-Event-Id` before applying the snapshot.
* Partition products by `organizationId` and `productId`.
* Replace the integration view from `data.product`; do not merge raw marketplace
  webhook bodies into this resource.
* Treat the specific price and inventory events as additional notifications,
  not as replacements for `product.updated` unless you subscribe that way.
* Remove or tombstone the local catalog record on `product.deleted`.
* On `product.delisted`, unpublish only the marketplaces named in
  `changedFields` (`marketplaces.shopee`, `marketplaces.lazada`, and so on).
  Keep the OmniCommerce product. `data.product.marketplaces` is the saved
  listing state after the delist.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.