Skip to main content

Catalog Webhook Integration Guide for Merchants

This document explains how an external merchant system integrates with Merchant Services through catalog webhooks. It is written for merchant engineering teams that own ERP, PIM, commerce platform, warehouse, marketplace, or custom product data systems.

The scope is catalog only:

  • Send product create, update, upsert, price, inventory, and delete events into Merchant Services.
  • Understand every product and variant field that can be posted.
  • Understand the product, cart, checkout, and order data that Merchant Services can send back to the merchant through outbound webhooks.
  • Implement a secure, reliable outbound webhook receiver.
  • Prepare network, security, retry, monitoring, and go-live controls.

1. Integration Model​

Merchant Services expects the merchant's source system to remain the system of record for product data. Merchant Services stores a synchronized catalog copy so the merchant can use catalog APIs, cart creation, checkout preparation, order events, and connected commerce flows.

Use webhook integration when the merchant wants event-driven synchronization. Use REST batch import when the merchant wants scheduled full syncs. Many production deployments use both: a nightly REST reconciliation job plus near-real-time webhooks for product changes.

2. Required Values​

Before development starts, Thyris or your integration owner will provide the required connection values. Do not infer these values from another environment.

ValueDescription
webhookBaseUrlPublic webhook ingress host shared by Thyris or your integration owner, for example https://webhook.thyris.ai or a customer-specific custom domain.
apiKeyCatalog webhook API key shared with you for the target environment.
merchantIdMerchant account identifier shared for support and reconciliation.
storeIdStore UUID shared by Thyris or your integration owner. Every product webhook must include the correct store.
sku policyMerchant-owned SKU format and uniqueness rules agreed during integration.
externalId policySource-system product ID format agreed during integration. Strongly recommended.
outbound destination URLMerchant-owned public HTTPS endpoint that you provide to receive catalog events.
outbound secretShared secret agreed for outbound delivery. Merchant Services sends it as x-webhook-secret.
staging hostsStaging webhook ingress and merchant receiver URLs shared for testing.
production contactsTechnical owner, escalation contact, and incident channel.

3. Inbound Catalog Webhook​

Inbound catalog webhooks are HTTP POST requests from the merchant system to Merchant Services.

Endpoint​

POST /webhooks/catalog
Authorization: Bearer tr_live_your_key_here
Content-Type: application/json

Webhook URL:

{webhookBaseUrl}/webhooks/catalog

webhookBaseUrl may be a Thyris-hosted domain or a customer-specific custom domain. Always use the exact domain, protocol, and path shared by Thyris or your integration owner.

The same endpoint accepts single-product and batch events.

4. Inbound Authentication​

Use a developer API key.

Authorization: Bearer tr_live_your_key_here

Alternative header:

X-Catalog-API-Key: tr_live_your_key_here

X-API-Key is also accepted for shared tooling, but Authorization: Bearer ... is the recommended production header and X-Catalog-API-Key is the recommended catalog-specific alternative.

Security requirements:

  • Keep the key server-side only.
  • Do not call the webhook endpoint from browser JavaScript or mobile apps.
  • Use a store-scoped key when one integration syncs one store.
  • Use a merchant-scoped key only when one backend service syncs multiple stores.
  • Rotate the key before production launch.
  • Rotate the key after vendor, employee, or credential exposure changes.
  • Store keys in a secret manager or encrypted runtime configuration.
  • Never place keys in product metadata, logs, screenshots, Git repositories, or merchant/customer-visible fields.

5. Supported Inbound Events​

EventMeaningProduct body
product.createdCreate a new product.product
product.updatedUpdate an existing product or create if not found by identifiers.product
product.upsertedCreate or update a product. Recommended default.product
product.inventory.updatedUpdate stock status or stock quantity.product
product.price.updatedUpdate price and currency.product
product.deletedDelete a product by storeId plus sku and/or externalId.product
catalog.batch.upsertedCreate or update multiple products.products
catalog.batch.deletedDelete multiple products.products

Recommended event strategy:

  • Use product.upserted for most create/update flows.
  • Use product.inventory.updated when only stock fields change.
  • Use product.price.updated when only price fields change.
  • Use status: "archived" instead of hard delete when historical visibility matters.
  • Use product.deleted only when the merchant wants the product removed from active catalog storage.

6. Payload Envelope​

Single product:

{
"event": "product.upserted",
"direction": "inbound",
"source": "merchant-pim",
"product": {
"storeId": "STORE_UUID"
}
}

Batch:

{
"event": "catalog.batch.upserted",
"direction": "inbound",
"source": "merchant-pim",
"products": [
{
"storeId": "STORE_UUID"
}
]
}

Envelope fields:

FieldTypeRequiredDescription
eventstringYesOne of the supported catalog events.
directionstringNoUse inbound. Defaults to inbound when omitted.
sourcestringNoSource system name such as erp, pim, shop, or warehouse. Max 120 chars.
productobjectRequired for single eventsOne product payload.
productsarrayRequired for batch eventsProduct payload array. Max 250 products.

Send either product or products. Do not send both unless explicitly testing mixed fallback behavior.

7. Product Fields the Merchant Can Send​

This is the full supported product payload for inbound catalog webhooks.

FieldTypeRequiredValidationDescription
storeIdUUID stringYesMust be accessible by API key.Target store.
skustring or nullRecommendedMax 120 chars.Merchant SKU. Used for idempotent upsert in the store.
externalIdstring or nullRecommendedMax 255 chars.Product ID in the merchant's ERP/PIM/platform. Used for idempotent upsert.
namestringYes for upsert/update/create1-255 chars.Product display name.
descriptionstring or nullNoText.Product description used for search and product detail rendering.
pricenumberYes for upsert/update/createMust be >= 0.Current sell price.
currencystringNoExactly 3 letters. Default USD.ISO-style currency code such as USD, EUR, TRY, SAR.
statusstringNodraft, active, or archived. Default active.Product publication state.
inStockbooleanNotrue or false. Default true.Whether the product can currently be sold.
inventoryQuantityintegerNoMust be >= 0. Default 0.Available inventory quantity.
imageUrlstring, empty string, or nullNoValid URL when present.Backward-compatible primary product image URL.
imageUrlsstring[] or nullNoMaximum 20 valid URLs.Ordered gallery; first URL is synchronized to imageUrl.
productUrlstring, empty string, or nullNoValid URL when present.Public storefront product URL.
metadataobject or nullNoJSON object.Internal integration metadata and custom data provided by Thyris or your integration owner. Avoid PII and secrets.
otherDetailsobject or nullNoJSON object.Searchable product attributes and integration-provided custom catalog data such as brand, category, color, tags, or campaign fields.
variantsarrayNoArray of variant objects. Default [].Size/color/package variants stored under the product.

Identity rules:

  • storeId + sku should uniquely identify one product.
  • storeId + externalId should uniquely identify one product.
  • Send at least one of sku or externalId for every product.
  • If both identifiers are sent, they must refer to the same product.
  • Use the same identifiers on every retry.
  • Do not reuse an old SKU or external ID for a different product unless the old product has been intentionally retired and reconciled.

Status rules:

  • active: product can appear in search, carts, and customer-facing flows.
  • draft: product exists but should not be treated as generally available.
  • archived: product should be hidden from active selling flows while preserving historical context.

8. Variant Fields the Merchant Can Send​

Variants are stored inside the parent product payload.

FieldTypeRequiredValidationDescription
skustring or nullRecommendedMax 120 chars.Variant SKU.
externalIdstring or nullRecommendedMax 255 chars.Source-system variant ID.
namestringYes1-255 chars.Variant display name.
descriptionstring or nullNoText.Variant description.
pricenumberYesMust be >= 0.Variant sell price.
currencystringNoExactly 3 letters. Default USD.Variant currency.
statusstringNodraft, active, or archived. Default active.Variant status.
inStockbooleanNoBoolean. Default true.Variant stock state.
inventoryQuantityintegerNoMust be >= 0. Default 0.Variant stock quantity.
imageUrlstring, empty string, or nullNoValid URL when present.Variant image URL.
productUrlstring, empty string, or nullNoValid URL when present.Variant-specific product URL.
otherDetailsobject or nullNoJSON object.Variant attributes such as color, size, material.

Variant guidance:

  • Use variants when a customer can choose size, color, package, configuration, or region.
  • Keep parent product fields usable even when variants exist.
  • Put shared product attributes on the parent and option-specific attributes on each variant.
  • Do not send thousands of variants in one product. Split very large catalogs into separate products or discuss a custom model.

otherDetails is the best place for merchant-specific searchable product attributes and integration-provided custom catalog data. If Thyris or your integration owner shares additional campaign, segment, marketplace, loyalty, installment, or channel fields, place them here unless they are mapped to a dedicated top-level field.

Recommended fields:

{
"brand": "Example Brand",
"category": "Apparel",
"subcategory": "Hoodies",
"tags": ["hoodie", "black", "bestseller"],
"color": "black",
"material": "cotton",
"gender": "unisex",
"ageGroup": "adult",
"sizeSystem": "US",
"season": "winter",
"collection": "2026-core",
"barcode": "0123456789012",
"countryOfOrigin": "TR",
"campaignCode": "SUMMER-26",
"segment": "premium",
"installmentEligible": true,
"customData": {
"providedByIntegration": true,
"displayPriority": 10
},
"fulfillment": {
"warehouse": "Example City-main",
"shipsInDays": 2
}
}

Avoid putting credentials, private cost, private margin, raw customer data, or internal notes in otherDetails.

Use metadata for integration, operational information, and custom technical data provided by Thyris or your integration owner that helps reconciliation.

{
"sourceEventId": "evt_20260611_0001",
"sourceUpdatedAt": "2026-06-11T20:00:00Z",
"sourceSystem": "merchant-pim",
"syncJobId": "job_123",
"schemaVersion": "2026-06",
"integrationId": "integration_001",
"integrationProvidedCustomData": {
"routingGroup": "catalog-primary"
}
}

Do not use metadata for secrets, API keys, access tokens, customer PII, payment data, or browser-only state.

11. Complete Product Upsert Example​

{
"event": "product.upserted",
"direction": "inbound",
"source": "merchant-pim",
"product": {
"storeId": "STORE_UUID",
"sku": "HOODIE-BLK",
"externalId": "pim_product_123",
"name": "Black Hoodie",
"description": "A heavyweight black hoodie with brushed interior.",
"price": 49.9,
"currency": "USD",
"status": "active",
"inStock": true,
"inventoryQuantity": 12,
"imageUrl": "https://cdn.merchant.example/products/hoodie-black.png",
"imageUrls": [
"https://cdn.merchant.example/products/hoodie-black.png",
"https://cdn.merchant.example/products/hoodie-black-back.png"
],
"productUrl": "https://store.merchant.example/products/black-hoodie",
"metadata": {
"sourceEventId": "evt_20260611_0001",
"sourceUpdatedAt": "2026-06-11T20:00:00Z",
"schemaVersion": "2026-06"
},
"otherDetails": {
"brand": "Example Brand",
"category": "Apparel",
"subcategory": "Hoodies",
"tags": ["hoodie", "black"],
"material": "cotton",
"gender": "unisex"
},
"variants": [
{
"sku": "HOODIE-BLK-M",
"externalId": "pim_variant_123_m",
"name": "Black Hoodie / Medium",
"price": 49.9,
"currency": "USD",
"status": "active",
"inStock": true,
"inventoryQuantity": 8,
"imageUrl": "https://cdn.merchant.example/products/hoodie-black-m.png",
"productUrl": "https://store.merchant.example/products/black-hoodie?variant=medium",
"otherDetails": {
"color": "black",
"size": "M"
}
}
]
}
}

12. Price Update Example​

{
"event": "product.price.updated",
"source": "merchant-pricing",
"product": {
"storeId": "STORE_UUID",
"sku": "HOODIE-BLK",
"externalId": "pim_product_123",
"name": "Black Hoodie",
"price": 44.9,
"currency": "USD",
"status": "active"
}
}

Current webhook processing stores the product state from the payload. For partial operational events, still send enough product fields to keep the catalog record complete and valid.

13. Inventory Update Example​

{
"event": "product.inventory.updated",
"source": "warehouse",
"product": {
"storeId": "STORE_UUID",
"sku": "HOODIE-BLK",
"externalId": "pim_product_123",
"name": "Black Hoodie",
"price": 44.9,
"currency": "USD",
"status": "active",
"inStock": true,
"inventoryQuantity": 24
}
}

14. Batch Upsert Example​

{
"event": "catalog.batch.upserted",
"source": "merchant-pim",
"products": [
{
"storeId": "STORE_UUID",
"sku": "SKU-001",
"externalId": "pim_001",
"name": "Product 1",
"price": 19.9,
"currency": "USD",
"status": "active",
"inStock": true,
"inventoryQuantity": 20
},
{
"storeId": "STORE_UUID",
"sku": "SKU-002",
"externalId": "pim_002",
"name": "Product 2",
"price": 29.9,
"currency": "USD",
"status": "active",
"inStock": false,
"inventoryQuantity": 0
}
]
}

Batch requirements:

  • Maximum products array size is 250.
  • All products must belong to stores accessible by the API key.
  • Use stable identifiers for every product.
  • Retry the full batch only if the sender can tolerate idempotent reprocessing.
  • For very large catalogs, send pages sequentially and keep a sync checkpoint in the merchant system.

15. Delete or Archive​

Hard delete by SKU or external ID:

{
"event": "product.deleted",
"source": "merchant-erp",
"product": {
"storeId": "STORE_UUID",
"sku": "HOODIE-BLK",
"externalId": "pim_product_123"
}
}

Archive instead of delete:

{
"event": "product.upserted",
"source": "merchant-erp",
"product": {
"storeId": "STORE_UUID",
"sku": "HOODIE-BLK",
"externalId": "pim_product_123",
"name": "Black Hoodie",
"price": 44.9,
"currency": "USD",
"status": "archived",
"inStock": false,
"inventoryQuantity": 0
}
}

Use archive when:

  • The product has been sold before.
  • Orders, carts, reports, or support tools may reference the product.
  • The merchant wants a reversible operation.

Use hard delete when:

  • The product was created by mistake.
  • The product must be removed from catalog storage.
  • Historical references are not required.

16. Inbound Responses​

Created:

{
"success": true,
"mode": "created",
"count": 1,
"results": [
{
"success": true,
"mode": "created",
"data": {
"id": "PRODUCT_UUID"
}
}
]
}

Updated:

{
"success": true,
"mode": "updated",
"count": 1,
"results": [
{
"success": true,
"mode": "updated",
"data": {
"id": "PRODUCT_UUID"
}
}
]
}

Batch:

{
"success": true,
"mode": "batch",
"count": 2,
"results": [
{
"success": true,
"mode": "created",
"data": {
"id": "PRODUCT_UUID_1"
}
},
{
"success": true,
"mode": "updated",
"data": {
"id": "PRODUCT_UUID_2"
}
}
]
}

mode values can include created, updated, deleted, ignored, or batch.

17. Product Data Merchant Can Receive​

When Merchant Services sends outbound product webhooks, product objects may include the following fields.

Outbound delivery is also triggered by catalog MCP writes. catalog_upsert_product, catalog_delete_product, catalog_create_cart, catalog_clear_cart, and catalog_complete_order emit their documented product/cart/checkout/order events with direction: "outbound" and source: "mcp" when the destination subscribes to those events.

FieldTypeDescription
idstringThyris product UUID.
catalogIdstring10-digit catalog ID used by cart and agent tools.
merchantIdstringMerchant account UUID.
storeIdstringStore UUID.
skustring or nullMerchant SKU.
externalIdstring or nullMerchant source-system product ID.
namestringProduct name.
descriptionstring or nullProduct description.
pricestring or numberProduct price. Some responses serialize decimals as strings.
currencystringCurrency code.
statusstringdraft, active, or archived.
inStockbooleanStock availability.
inventoryQuantityintegerAvailable quantity.
imageUrlstring or nullPrimary product image URL.
imageUrlsstring[] or nullOrdered product gallery, maximum 20 URLs.
productUrlstring or nullProduct page URL.
metadataobjectIntegration metadata.
otherDetailsobjectFlexible business attributes.
variantsarrayVariant objects.
sourcestringLast write source such as api, webhook, thyris, or another integration source.
createdAtstringISO timestamp.
updatedAtstringISO timestamp.

Example outbound product event:

{
"event": "product.updated",
"direction": "outbound",
"source": "thyris",
"product": {
"id": "PRODUCT_UUID",
"catalogId": "1234567890",
"merchantId": "MERCHANT_UUID",
"storeId": "STORE_UUID",
"sku": "HOODIE-BLK",
"externalId": "pim_product_123",
"name": "Black Hoodie",
"description": "A heavyweight black hoodie.",
"price": "44.90",
"currency": "USD",
"status": "active",
"inStock": true,
"inventoryQuantity": 24,
"imageUrl": "https://cdn.merchant.example/products/hoodie-black.png",
"imageUrls": [
"https://cdn.merchant.example/products/hoodie-black.png",
"https://cdn.merchant.example/products/hoodie-black-back.png"
],
"productUrl": "https://store.merchant.example/products/black-hoodie",
"metadata": {
"sourceEventId": "evt_20260611_0001"
},
"otherDetails": {
"brand": "Example Brand",
"category": "Apparel"
},
"variants": [],
"source": "thyris",
"createdAt": "2026-06-11T19:45:00Z",
"updatedAt": "2026-06-11T20:00:00Z"
}
}

18. Other Catalog Data Merchant Can Receive​

Outbound catalog webhooks can also notify the merchant about cart, checkout, and order activity.

Cart Fields​

FieldTypeDescription
idstringThyris cart UUID.
scopeobjectOwning { merchantId, storeId } pair.
checkout.idstringCheckout ID generated from cart; checkout is omitted for carts without one.
statusstringCart status such as open, checkout_created, or completed state.
itemsarrayCart line items.
totalQuantityintegerSum of item quantities.
totalAmountstringTotal amount.
currencystringCart currency.
metadataobjectIntegration metadata.
otherDetailsobjectIntegration or channel context.
createdAtstringISO timestamp.
updatedAtstringISO timestamp.

Cart item fields can include id, productId, catalogId, quantity, unitPrice, totalPrice, currency, productSnapshot, metadata, and otherDetails.

Order Fields​

FieldTypeDescription
idstringThyris order UUID.
orderIdstringPublic order identifier.
checkout.idstringCheckout ID used to create the order.
payment.idstringPayment reference supplied by the caller or generated by Catalog.
scopeobjectOwning merchant/store pair.
customerobjectCustomer information supplied during order completion.
shippingAddressobjectShipping address supplied during order completion.
billingAddressobjectBilling address when present.
paymentobjectSafe ID/provider/status data.
statusstringOrder status such as completed or cancelled.
itemsarrayImmutable order item snapshots.
totalQuantityintegerSum of item quantities.
totalAmountstringOrder total amount.
currencystringOrder currency.
metadataobjectIntegration metadata.
otherDetailsobjectIntegration-specific data.
createdAtstringISO timestamp.
completedAtstring or nullCompletion timestamp.

Example outbound order event:

{
"event": "order.completed",
"direction": "outbound",
"source": "thyris",
"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": []
}
}

19. Outbound Catalog Events​

Merchant Services can send these catalog events to the merchant's receiver.

EventTrigger
product.createdProduct created through API, webhook, or another approved catalog integration.
product.updatedProduct updated through API, webhook, or another approved catalog integration.
product.upsertedGeneric upsert event when included in the agreed outbound event set.
product.deletedProduct deleted.
product.inventory.updatedStock status or quantity changed.
product.price.updatedPrice changed.
catalog.batch.upsertedBatch upsert processed.
catalog.batch.deletedBatch delete processed.
cart.createdCart created.
cart.updatedCart changed.
cart.clearedCart cleared.
checkout.createdCheckout ID created for a cart.
order.createdOrder created.
order.updatedOrder changed.
order.completedOrder completed.
order.cancelledOrder cancelled.

Outbound delivery rules:

  • Merchant Services sends outbound webhooks as HTTP POST.
  • Content-Type is application/json.
  • If an outbound secret is agreed for the integration, Merchant Services sends x-webhook-secret.
  • The merchant endpoint must respond within 10 seconds.
  • Any 2xx status is treated as success.
  • Non-2xx, timeout, DNS, TLS, invalid URL, and network errors are logged as failed delivery.

20. Implementing the Merchant Receiver​

The receiver is a merchant-owned HTTPS endpoint that accepts outbound catalog events.

Minimum behavior:

  1. Accept POST only.
  2. Enforce HTTPS.
  3. Validate Content-Type: application/json.
  4. Validate x-webhook-secret if an outbound secret was agreed.
  5. Parse JSON.
  6. Validate event.
  7. Extract stable identifiers.
  8. Deduplicate.
  9. Persist or enqueue the payload.
  10. Return 2xx quickly.

Recommended receiver path:

POST https://integrations.merchant.example/webhooks/merchant-services/catalog

Do not block the HTTP response on slow ERP, OMS, PIM, or warehouse writes. Store the payload and process it asynchronously.

Receiver Pseudocode​

on POST /webhooks/merchant-services/catalog:
reject if method is not POST
reject if content type is not JSON
reject if x-webhook-secret is missing or wrong

parse JSON
validate event
derive idempotency key:
event + product.id/catalogId/sku/externalId
or event + cart.id/cart.checkout.id
or event + order.orderId/id

if idempotency key has already been accepted:
return 200

save raw payload and headers
enqueue downstream processing job
return 202

Node.js Receiver Example​

import express from "express"

const app = express()
app.use(express.json({ limit: "1mb" }))

const WEBHOOK_SECRET = process.env.MERCHANT_SERVICES_WEBHOOK_SECRET

app.post("/webhooks/merchant-services/catalog", async (req, res) => {
if (WEBHOOK_SECRET && req.header("x-webhook-secret") !== WEBHOOK_SECRET) {
return res.status(401).json({ error: "invalid webhook secret" })
}

const event = String(req.body?.event || "")
if (!event) {
return res.status(400).json({ error: "event is required" })
}

const product = req.body?.product
const cart = req.body?.cart
const order = req.body?.order
const stableId =
product?.id ||
product?.catalogId ||
product?.sku ||
product?.externalId ||
cart?.id ||
cart?.checkout?.id ||
order?.orderId ||
order?.id

if (!stableId) {
return res.status(400).json({ error: "stable object identifier is required" })
}

const idempotencyKey = `${event}:${stableId}`
await saveCatalogWebhookEvent({
idempotencyKey,
event,
payload: req.body,
headers: {
"x-webhook-secret-present": Boolean(req.header("x-webhook-secret"))
},
receivedAt: new Date().toISOString()
})

return res.status(202).json({ received: true })
})

app.listen(3000)

Replace saveCatalogWebhookEvent with the merchant's database, queue, or event bus implementation.

21. Network and Connectivity Requirements​

Inbound to Merchant Services:

  • Merchant source system must reach the webhookBaseUrl provided by Thyris or your integration owner.
  • TLS 1.2 or newer is recommended.
  • Payloads must be JSON over HTTPS.
  • Requests should come from stable backend infrastructure where possible.
  • Merchant firewalls should allow outbound HTTPS to the webhook ingress host.
  • Do not send webhooks through a user's browser.

Outbound to merchant:

  • Merchant destination URL must be public HTTPS.
  • Hostname must resolve in public DNS.
  • URL must not resolve to private, loopback, multicast, or internal network IPs.
  • Endpoint must accept HTTP POST.
  • Endpoint must respond within 10 seconds.
  • Endpoint should support normal internet egress from Merchant Services delivery infrastructure.
  • If IP allowlisting is required, coordinate the current egress ranges with Thyris or your integration owner.
  • Use a stable hostname rather than a raw IP.
  • Use a valid certificate from a public certificate authority.

Unsupported outbound destinations:

  • http:// URLs.
  • localhost.
  • RFC1918 private IP addresses.
  • VPN-only hosts.
  • Internal Kubernetes service names.
  • Self-signed TLS certificates.
  • URLs requiring interactive login.

22. Security Controls​

Required controls:

  • Store API keys and webhook secrets in a secret manager.
  • Validate outbound x-webhook-secret.
  • Log request IDs and event IDs, not secrets.
  • Redact Authorization, x-api-key, and x-webhook-secret in logs.
  • Use least-privilege store-scoped keys when possible.
  • Separate staging and production credentials.
  • Rotate credentials on a schedule.
  • Disable credentials immediately after suspected exposure.

Recommended controls:

  • Maintain an allowlist of expected event names.
  • Maintain an allowlist of expected storeId values.
  • Reject payloads larger than the agreed limit.
  • Rate limit the merchant receiver endpoint.
  • Persist raw event payloads in an access-controlled store for audit and replay.
  • Monitor repeated failures, unexpected event types, and spikes.
  • Keep a schema version in metadata.schemaVersion.
  • Use source event IDs in metadata.sourceEventId.

23. Idempotency​

Inbound sender:

  • Use stable storeId + sku or storeId + externalId.
  • Include metadata.sourceEventId when the source system has an event ID.
  • Retry using the same payload and identifiers.
  • Treat repeated product.upserted calls as safe.
  • Treat delete of an already deleted product as non-fatal.

Outbound receiver:

  • Deduplicate by event plus stable object identifier.
  • Product key preference: product.id, product.catalogId, product.sku, product.externalId.
  • Cart key preference: cart.id, cart.checkout.id.
  • Order key preference: order.orderId, order.id.
  • Store idempotency keys long enough to cover retry windows and replay operations.

24. Retry and Backoff​

Merchant inbound sender should retry:

  • 408 Request Timeout
  • 409 Conflict when the conflict is expected to clear
  • 425 Too Early
  • 429 Too Many Requests
  • 5xx server errors
  • Network timeouts

Merchant inbound sender should not retry unchanged payloads for:

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • Schema validation errors
  • Store scope errors

Suggested backoff:

Merchant outbound receiver should:

  • Return 202 after accepting and queueing the event.
  • Return 200 for duplicate but already accepted events.
  • Return 401 for invalid secrets.
  • Avoid 500 for downstream system downtime when the event can be queued.

25. Testing Checklist​

Inbound tests:

  • Send one valid product.upserted.
  • Send the same product.upserted again and verify no duplicate product is created.
  • Send product.price.updated.
  • Send product.inventory.updated.
  • Send catalog.batch.upserted with at least two products.
  • Send product.deleted or archive payload.
  • Send invalid API key and verify 401.
  • Send missing storeId and verify validation failure.
  • Send invalid currency and verify validation failure.
  • Send unsupported status and verify validation failure.

Outbound receiver tests:

  • Provide a staging HTTPS destination to Thyris or your integration owner.
  • Validate the agreed x-webhook-secret.
  • Trigger product.created or product.updated.
  • Verify receiver validates secret.
  • Verify receiver logs payload and returns 2xx.
  • Trigger duplicate payload and verify idempotency.
  • Trigger cart/order events if those flows are in scope.
  • Force receiver timeout in staging and verify failure is logged.
  • Restore receiver and confirm successful delivery.

Operational tests:

  • Rotate API key in staging.
  • Rotate outbound secret in staging.
  • Confirm monitoring alerts for failed inbound sends.
  • Confirm monitoring alerts for failed outbound receives.
  • Confirm replay process for queued receiver jobs.

26. Go-Live Checklist​

Credentials:

  • Production API key received from Thyris or your integration owner.
  • Production key scope confirmed.
  • Production key is not reused from staging.
  • Outbound secret agreed and tested.
  • Key and secret are stored securely.

Product data:

  • storeId mapping is final.
  • SKU policy is final.
  • External ID policy is final.
  • Required fields are always present.
  • Product status mapping is final.
  • Delete vs archive policy is signed off.
  • Image URLs are public and durable.
  • Product URLs are public and durable.
  • metadata and otherDetails do not contain secrets or unnecessary PII.

Network:

  • Merchant source system can reach webhook ingress.
  • Merchant receiver has public HTTPS.
  • DNS and TLS are valid.
  • Firewall rules are configured.
  • Receiver responds within 10 seconds.

Operations:

  • Retry policy implemented.
  • Idempotency implemented.
  • Raw event logging implemented.
  • Alerting configured.
  • Replay process documented.
  • Support contacts exchanged.
  • Staging UAT complete.
  • Production smoke test complete.

27. Common Errors​

ErrorLikely causeFix
401 UnauthorizedMissing or invalid API key.Check environment, key value, and key scope.
403 ForbiddenAPI key cannot access the target store.Use correct storeId or key scope.
400 Invalid JSON payloadBody is not valid JSON.Validate JSON before sending.
400 Either product or products is requiredPayload has no product object.Add product or products.
400 storeId is requiredProduct missing store ID.Include assigned storeId.
400 name is requiredUpsert payload missing product name.Include name.
400 price must be greater than or equal to 0Negative price.Send zero or positive value.
400 currency must be a 3-letter codeCurrency is not exactly 3 letters.Send USD, EUR, TRY, SAR, etc.
400 status must be draft, active, or archivedUnsupported status.Map source statuses to allowed values.
409 SKU and externalId match different catalog productsIdentifiers point to different existing products.Fix source mapping and reconcile product identities.
Outbound URL rejectedURL is not public HTTPS or resolves privately.Use a public HTTPS endpoint.
Outbound timeoutReceiver did not respond within 10 seconds.Queue work and respond quickly.

28. Merchant Ownership​

Merchant owns:

  • Product source data accuracy.
  • Product identifier stability.
  • Product status mapping.
  • Inbound webhook sender.
  • Outbound webhook receiver.
  • Receiver idempotency.
  • Receiver retry/replay process.
  • Source system reconciliation.
  • Secure storage of API keys and webhook secrets.

Merchant Services owns:

  • Public webhook ingress.
  • API key validation.
  • Store scope enforcement.
  • Catalog persistence.
  • Outbound webhook dispatch.
  • Delivery logging.
  • Outbound webhook delivery setup.
  • Catalog API product availability.