Catalog Webhooks
Thyris supports both inbound and outbound catalog webhooks.
- Inbound webhooks let external systems push product updates into Thyris.
- Outbound webhooks let Thyris notify merchant systems when catalog, cart, checkout, or order events happen.
For required and optional webhook fields, see 05-request-field-requirements.md.
Inbound Webhook Endpoint
POST /webhooks/catalog
Authorization: Bearer tr_live_your_key_here
Content-Type: application/json
Production URL:
https://webhooks.thyris.cloud/webhooks/catalog
Thyris validates the developer API key, enforces store scope, and processes accepted catalog payloads according to the documented webhook contract.
Inbound Events
| Event | Purpose |
|---|---|
product.created | Create a product. |
product.updated | Update a product. |
product.upserted | Create or update a product using SKU/external ID. |
product.deleted | Delete a product using SKU/external ID. |
product.inventory.updated | Update product stock fields. |
product.price.updated | Update product price fields. |
catalog.batch.upserted | Create or update multiple products. |
catalog.batch.deleted | Delete multiple products. |
cart.created | Create a cart from inbound webhook payload. |
cart.item.added / cart.bundle.added | Add one or more items to an existing open cart. |
cart.item.updated / cart.bundle.updated | Replace quantities for one or more existing cart lines. |
cart.item.removed / cart.bundle.removed | Remove one or more cart lines. |
cart.archived | Archive an existing cart permanently. |
order.completed | Complete an order from an existing checkout. |
Create/update/upsert product events require name and price. Product delete events identify products by sku and/or externalId within the store. Cart mutation events require cart.cartId; add events require line catalogId and quantity, while update/remove events accept line itemId or catalogId.
For product.inventory.updated, inventoryQuantity is an absolute current-stock snapshot, not a decrement command. If order completion has already reduced a local stock copy, set the value to the merchant snapshot; do not subtract it again. Include a stable source event ID and source version/timestamp so duplicate or older snapshots can be ignored.
Inbound and outbound catalog webhook envelopes may include optional top-level version and timestamp. When omitted on inbound requests, Thyris normalizes metadata for processing. Outbound deliveries always include version and timestamp; receivers should store them with the deduplication record and may reject older source snapshots according to merchant policy.
Inbound Single Product Upsert
{
"event": "product.upserted",
"source": "shopify",
"product": {
"storeId": "STORE_UUID",
"sku": "HOODIE-BLK",
"externalId": "shopify_product_123",
"name": "Black Hoodie",
"description": "A heavyweight black hoodie.",
"price": 49.9,
"currency": "USD",
"status": "active",
"inStock": true,
"inventoryQuantity": 12,
"imageUrl": "https://cdn.example.com/hoodie.png",
"imageUrls": [
"https://cdn.example.com/hoodie.png",
"https://cdn.example.com/hoodie-back.png"
],
"productUrl": "https://store.example.com/products/hoodie",
"otherDetails": {
"brand": "Acme",
"category": "Apparel"
},
"variants": [
{
"sku": "HOODIE-BLK-M",
"externalId": "shopify_variant_123",
"name": "Black Hoodie / Medium",
"price": 49.9,
"currency": "USD",
"status": "active",
"inStock": true,
"inventoryQuantity": 8,
"otherDetails": {
"color": "black",
"size": "M"
}
}
]
}
}
Inbound Batch Upsert
{
"event": "catalog.batch.upserted",
"source": "pim",
"products": [
{
"storeId": "STORE_UUID",
"sku": "SKU-001",
"externalId": "pim_001",
"name": "Product 1",
"price": 19.9,
"currency": "USD"
},
{
"storeId": "STORE_UUID",
"sku": "SKU-002",
"externalId": "pim_002",
"name": "Product 2",
"price": 29.9,
"currency": "USD"
}
]
}
Maximum batch size is 250 products.
Inbound Delete
{
"event": "product.deleted",
"source": "erp",
"product": {
"storeId": "STORE_UUID",
"sku": "HOODIE-BLK",
"externalId": "shopify_product_123"
}
}
If the product is not found, the delete request is treated as ignored rather than a hard failure.
Inbound Cart Mutation Examples
Add a bundle to an existing cart:
{
"event": "cart.bundle.added",
"source": "merchant-oms",
"version": "merchant.cart.v2@2026-09-12T18:00:00Z",
"timestamp": "2026-09-12T18:00:00Z",
"cart": {
"cartId": "CART_UUID",
"items": [
{ "catalogId": "1234567890", "quantity": 1 },
{ "catalogId": "9876543210", "quantity": 2 }
]
}
}
Archive an existing cart:
{
"event": "cart.archived",
"source": "merchant-oms",
"cart": {
"cartId": "CART_UUID"
}
}
Archived carts are not restored by webhook, API, MCP, or dashboard operation.
Inbound Response
{
"success": true,
"mode": "updated",
"count": 1,
"data": {
"id": "product_uuid"
},
"results": [
{
"success": true,
"mode": "updated",
"data": {
"id": "product_uuid"
}
}
]
}
mode can be created, updated, deleted, ignored, or batch.
Outbound Webhook Destinations
Outbound destinations are configured in the Merchant Dashboard under the store's catalog integrations area.
Required fields:
- Name
- Public HTTPS destination URL
- Selected events
Optional field:
- Secret header
Private network targets and non-HTTPS URLs are rejected.
Catalog destinations only receive catalog events selected for that destination. Procurement destinations are configured separately.
Outbound Events
Catalog writes performed through MCP are not silent. The catalog engine dispatches the same configured outbound webhooks as REST/dashboard flows, with direction: "outbound" and source: "mcp".
| Event | Trigger |
|---|---|
product.created | Product created through API, dashboard, or catalog_upsert_product. |
product.updated | Product updated through API, dashboard, or catalog_upsert_product. |
product.upserted | Generic upsert event where configured. |
product.deleted | Product deleted, including through catalog_delete_product. |
product.inventory.updated | Stock quantity or stock status changed. |
product.price.updated | Price changed. |
catalog.batch.upserted | Batch upsert event. |
catalog.batch.deleted | Batch delete event. |
cart.created | Cart created, including through catalog_create_cart or cart_create. |
cart.item.added | One item added after cart creation. |
cart.item.updated | One cart line quantity updated. |
cart.item.removed | One cart line removed. |
cart.bundle.added | Multiple items added in one atomic bundle mutation. |
cart.bundle.updated | Multiple cart lines updated in one atomic bundle mutation. |
cart.bundle.removed | Multiple cart lines removed in one atomic bundle mutation. |
cart.updated | Compatibility event emitted after item or bundle mutation. |
cart.cleared | Cart cleared, including through catalog_clear_cart or cart_clear. |
cart.closed | Open cart closed without checkout. |
cart.archived | Cart archived permanently. |
checkout.created | Checkout ID created for a cart, including checkout preparation. |
checkout.cancelled | Prepared checkout cancelled. |
order.created | Order created, including through catalog_complete_order. |
order.updated | Order changed. |
order.completed | Order completed, including through catalog_complete_order. |
order.cancelled | Order cancelled. |
Outbound Headers
Content-Type: application/json
x-webhook-secret: optional_shared_secret
x-webhook-secret is sent only when a secret is configured for the destination.
Outbound Product Payload
{
"event": "product.updated",
"direction": "outbound",
"source": "api",
"version": "catalog.v1@2026-09-12T18:00:00Z",
"timestamp": "2026-09-12T18:00:00Z",
"product": {
"scope": {
"merchantId": "MERCHANT_UUID",
"storeId": "STORE_UUID"
},
"sku": "HOODIE-BLK",
"externalId": "shopify_product_123",
"name": "Black Hoodie",
"price": 44.9,
"currency": "USD",
"status": "active",
"inStock": true,
"inventoryQuantity": 24,
"imageUrls": [
"https://cdn.example.com/hoodie.png",
"https://cdn.example.com/hoodie-back.png"
]
}
}
For MCP-triggered delivery, the same payload uses "source": "mcp". A destination receives the event only when that event is selected in its configuration.
MCP Event Matrix
| MCP Tool | Outbound Events |
|---|---|
catalog_upsert_product | product.created or product.updated |
catalog_delete_product | product.deleted |
catalog_create_cart / cart_create | cart.created, and checkout creation when the compatibility cart-create flow prepares checkout immediately |
catalog_mutate_cart_items, cart_add_item, cart_update_item, cart_remove_item | cart.item.added, cart.item.updated, cart.item.removed, plus cart.updated |
cart_add_bundle, cart_update_bundle, cart_remove_bundle | cart.bundle.added, cart.bundle.updated, cart.bundle.removed, plus cart.updated |
catalog_clear_cart / cart_clear | cart.cleared |
catalog_archive_cart / cart_archive | cart.archived |
cart_close | cart.closed |
checkout_prepare | checkout.created |
checkout_cancel | checkout.cancelled |
catalog_complete_order | order.created, order.completed, and product.inventory.updated for affected products |
Outbound Order Payload
{
"event": "order.completed",
"direction": "outbound",
"source": "api",
"order": {
"id": "order_uuid",
"orderId": "ord_1234567890abcdef12345678",
"scope": {
"merchantId": "merchant_uuid",
"storeId": "store_uuid"
},
"cart": {
"id": "cart_uuid"
},
"checkout": {
"id": "chk_1234567890abcdef12345678"
},
"payment": {
"id": "pay_1234567890abcdef12345678",
"status": "paid"
},
"status": "completed",
"totalQuantity": 2,
"totalAmount": "99.80",
"currency": "USD",
"items": []
}
}
Delivery Behavior
- Thyris sends outbound webhooks as HTTP
POST. - Destination must respond within 10 seconds.
- Any
2xxstatus is treated as success. - Non-
2xx, timeout, DNS, TLS, URL validation, or network errors are logged as failed delivery. - Delivery logs are visible in the dashboard.
Receiver Recommendations
- Make receivers idempotent by event type and product/order identifiers.
- Return
2xxquickly and process heavier work asynchronously. - Validate
x-webhook-secretif configured. - Do not rely on event delivery order across different destinations.
- Keep the endpoint public HTTPS.