Skip to content
API Docs

Event catalog

Every webhook event the integration emits, what it means, and the shape of data.object. The envelope wrapping each payload is documented on the Webhooks page.

Subscribing to events

Subscriptions are managed from Custom Integration → Settings → Event subscriptions. The default is the wildcard *: your endpoint receives every event.

  • Tick individual events to allowlist them.
  • Wildcard a namespace with e.g. listing.* (subscribe to all listing events).
  • integration.test_event ignores subscriptions; it's always delivered when fired manually.

Echo-suppression

Don't expect to receive your own writes back

Your own API calls are answered by their responses, not by events. Creating an order, cancelling it through POST /v1/orders/{id}/cancel or upserting a customer sends nothing to your URL, and a watch that sells through an order you reported does not come back as listing.sold. This is what stops trivial event-loops between your custom site and WatchTraderHub.

Events triggered elsewhere (other integrations like Shopify or eBay, actions taken in the dashboard) still fire to your URL as normal.

Events

Listing events only carry the id

For any listing.* event, data.object is just the listing id and object: "listing". To get the watch (brand, model, price, photos, caliber, complications, water resistance, power reserve), fetch GET /v1/listings/{id} when the event arrives. A permanent deletion sends listing.unpublished with deleted: true. Do not fetch that listing because it has already been removed and the API will return 404. Order events are the exception: they include the full order inline, exactly as GET /v1/orders/{id} returns it.

Spare parts are a separate namespace

A spare part is not a listing: it has a quantity and no watch. Part events therefore live under part.*, and a subscription to listing.* does not receive them. Fetch GET /v1/parts/{id} when one arrives, except for part.unpublished, where the part is already off the channel and the API will return 404.

Listing events

listing.published

Fires when the dealer toggles an inventory item ON for the custom channel.

data.object

{
  "id": "24f0ebb1-73d7-4e99-9901-821078594271",
  "object": "listing"
}

listing.updated

Fires when a published listing's data changes (price, photos, watch attributes, ...).

data.object

{
  "id": "24f0ebb1-73d7-4e99-9901-821078594271",
  "object": "listing"
}

data.previous_attributes: Reserved: currently always an empty object. Fetch the listing to read its current values.

listing.unpublished

Fires when the dealer toggles a listing OFF for the custom channel.

data.object

{
  "id": "24f0ebb1-73d7-4e99-9901-821078594271",
  "object": "listing"
}

listing.sold

Fires when a listing transitions to sold (either manually or via order matching).

data.object

{
  "id": "24f0ebb1-73d7-4e99-9901-821078594271",
  "object": "listing"
}

listing.in_stock

Fires when a previously-sold listing returns to active stock (e.g. sale voided).

data.object

{
  "id": "24f0ebb1-73d7-4e99-9901-821078594271",
  "object": "listing"
}

Spare part events

part.published

Fires when the dealer puts a spare part ON the custom channel. Fetch GET /v1/parts/{id} for the part.

data.object

{
  "id": "9c2f0a71-5d18-4a5e-9b64-0f1c2e3d4a5b",
  "object": "part"
}

part.updated

Fires when a part already on the channel changes (price, quantity, photos, compatibility, ...).

data.object

{
  "id": "9c2f0a71-5d18-4a5e-9b64-0f1c2e3d4a5b",
  "object": "part"
}

part.unpublished

Fires when the dealer takes a spare part OFF the custom channel. The part stops being readable once this arrives, so do not fetch it.

data.object

{
  "id": "9c2f0a71-5d18-4a5e-9b64-0f1c2e3d4a5b",
  "object": "part"
}

part.sold_out

Fires when a part reaches zero units, whether it was sold in WatchTraderHub or through a reported order. The part stays readable and its status reads sold_out.

data.object

{
  "id": "9c2f0a71-5d18-4a5e-9b64-0f1c2e3d4a5b",
  "object": "part",
  "status": "sold_out"
}

part.in_stock

Fires when a live part's stock count changes and it still has units: a restock, a return, a cancelled sale, a fit, or a partial sale. The new count is in quantity.

data.object

{
  "id": "9c2f0a71-5d18-4a5e-9b64-0f1c2e3d4a5b",
  "object": "part",
  "quantity": 3
}

Order events

order.fulfilled

Fires when an order is matched to a listing and the dealer marks it fulfilled.

data.object

{
  "id": "d7df4fbe-2c0c-4328-a3b0-a32c68cabd43",
  "object": "order",
  "external_id": "shop-12345",
  "status": "matched",
  "total_amount": 1500000,
  "currency": "USD",
  "buyer": { "name": "Alice", "email": "alice@example.com", "country": "US" },
  "line_items": [{ "sku": "116610LN", "quantity": 1, "unit_amount": 1500000 }],
  "ordered_at": "2026-05-08T08:41:16.345+00:00",
  "created_at": "2026-05-08T08:41:16.770507+00:00"
}

order.cancelled

Fires when the dealer dismisses one of your orders in WatchTraderHub. Not sent for a cancel you made yourself through `POST /v1/orders/{id}/cancel`: the response already tells you.

data.object

{
  "id": "d7df4fbe-2c0c-4328-a3b0-a32c68cabd43",
  "object": "order",
  "external_id": "shop-12345",
  "status": "dismissed",
  "total_amount": 1500000,
  "currency": "USD",
  "buyer": { "name": "Alice", "email": "alice@example.com", "country": "US" },
  "line_items": [{ "sku": "116610LN", "quantity": 1, "unit_amount": 1500000 }],
  "ordered_at": "2026-05-08T08:41:16.345+00:00",
  "created_at": "2026-05-08T08:41:16.770507+00:00"
}

data.previous_attributes: Reserved: currently always an empty object. The order payload carries the new status.

Integration events

integration.test_event

Fires from `POST /v1/test/webhook_ping`. Always delivered regardless of `subscribed_events` filtering.

data.object

{
  "message": "Test event from /v1/test/webhook_ping."
}