> For the complete documentation index, see [llms.txt](https://docs.violet.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.violet.io/ecom-platforms/shopify/sales-channel/catalog-sync.md).

# Catalog Sync

What publication-scoped syncing changes for your Violet API integration: which products you receive, the product lifecycle, and webhook behavior.

This page is for developers building against the Violet API for channels configured as a Shopify sales channel. It assumes the concepts on [Sales Channel Publishing on Shopify](/ecom-platforms/shopify/sales-channel.md).

## What changes for your integration

Your catalog for a Shopify merchant is now the set of products that merchant has published to your channel, not their whole store. Three consequences worth designing for:

1. **Catalog size is merchant-controlled and can change without warning.** A merchant can unpublish a hundred products in one bulk action. Your integration should handle the catalog shrinking as a normal event, not an error condition.
2. **A newly connected merchant may sync zero products.** Products are not published to a new channel automatically. Until the merchant publishes some, their catalog is legitimately empty. Do not treat an empty catalog after connection as a failed sync.
3. **Unpublishing is an update, not a deletion.** The Offer stays exactly where it is (same id, same data, same SKUs) with its `publishing_status` set to `NOT_PUBLISHED`. Read that field to know whether an Offer belongs in your catalog. See [Webhooks](#webhooks) below.

Everything else (order submission, inventory, pricing, refunds, the shape of the [Offer](/prism/catalog/offers.md) and [SKU](/prism/catalog/skus.md) models) is unchanged.

## Which products you receive

A product appears in your catalog only when **all** of the following are true:

* The merchant has published it to your channel in Shopify.
* Its Shopify status is Active.
* It is not excluded by the channel's existing catalog rules: collection filters, excluded collections, excluded tags, or excluded vendors. Publication filtering is applied *in addition to* these rules, not instead of them.

Publication filtering is applied consistently on every path that reads products: the product count, the paged catalog sync, the bulk catalog sync, the per-product webhook sync, and the direct product sync API. There is no route through which an unpublished product can enter your catalog.

## Product lifecycle

| Merchant action in Shopify                    | What Violet does                                         | Resulting Offer state                                                                                    | Webhook                                                                                                                   |
| --------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Publishes a product to your channel           | Fetches the product and creates or republishes the Offer | `publishing_status: PUBLISHED`                                                                           | `OFFER_ADDED` for a newly created Offer; `OFFER_UPDATED` **and** `OFFER_ADDED` when republishing one that already existed |
| Edits a published product                     | Re-fetches and updates the Offer                         | Offer updated                                                                                            | `OFFER_UPDATED`, when there are material changes                                                                          |
| Unpublishes the product from your channel     | Withdraws the Offer from your catalog                    | `publishing_status: NOT_PUBLISHED`. **`status`, `available`, `visible`, and SKU statuses are unchanged** | `OFFER_UPDATED` **and** `OFFER_REMOVED`                                                                                   |
| Deletes the product in Shopify                | Archives the Offer                                       | `status: ARCHIVED`, `available: false`, `visible: false`, every SKU `status: ARCHIVED`                   | `OFFER_UPDATED`                                                                                                           |
| Re-publishes a previously unpublished product | Re-fetches and republishes the Offer                     | `publishing_status: PUBLISHED` again, same Offer id                                                      | `OFFER_UPDATED` and `OFFER_ADDED`                                                                                         |

Note the difference between the middle two rows. **Unpublishing from your channel is neither an archive nor a deletion.** It is an update to one field. The product is still active and selling in the merchant's own store; only its membership of your catalog changed, so its status and availability are left untouched. A product actually deleted in Shopify still archives, because it genuinely is gone.

Publish and unpublish are event-driven. In normal operation the catalog reflects a merchant's change within seconds, though delivery is not instantaneous and should not be relied on as synchronous.

## Webhooks

The event name is delivered in the `X-Violet-Topic` header. The request body is the Offer itself. For the full offer event reference, see [Offer Webhooks](/prism/webhooks/events/offer-webhooks.md).

### Unpublishing is an update, not a deletion

When a merchant unpublishes a product from your channel, Violet sets the Offer's `publishing_status` to `NOT_PUBLISHED` and sends **both** `OFFER_UPDATED` and `OFFER_REMOVED`.

Despite the event name, nothing is deleted. The Offer keeps its id, its data, and its SKUs; only the publishing status changed. That is what allows a re-publish to restore the same Offer rather than create a new one. (`OFFER_DELETED` is a separate event, for Offers that are genuinely removed.)

**`publishing_status` is the field that tells you whether an Offer belongs in your catalog.** `status` and `available` describe the product in the merchant's own store, and an unpublished product is often still `AVAILABLE` there because it is still selling on their other channels. Both values are accurate; they answer different questions.

```
X-Violet-Topic: OFFER_REMOVED
```

```json
{
  "id": 4821904,
  "merchant_id": 17240,
  "external_id": "7382910481234",
  "publishing_status": "NOT_PUBLISHED",
  "status": "AVAILABLE",
  "available": true,
  "visible": true,
  "skus": [
    { "id": 91827364, "external_id": "42918273645", "status": "AVAILABLE" },
    { "id": 91827365, "external_id": "42918273646", "status": "AVAILABLE" }
  ]
}
```

The two events are delivered independently and may arrive in either order, so don't depend on the sequence; `publishing_status` is correct in both.

### Other webhook behavior

* **Offers you never received stay silent.** If an Offer was never published to your app, no webhook is sent when it changes. This matches existing Violet behavior.
* **`OFFER_UPDATED` is suppressed when nothing material changed.** A re-sync that produces an identical Offer does not generate an event.
* **Relay channels receive additional events.** Channels on the [Relay](/relay/relay.md) plan also receive `RELAY_OFFER_UPDATED` when a product is published or edited, and `RELAY_OFFER_REMOVED` when it is unpublished, delivered directly from the sync layer.
* **Your channel's catalog rules still apply.** If a product is withheld by a collection, tag, or vendor rule (or if offers for that merchant are configured not to publish automatically), ticking the channel in Shopify does not override that. Channel membership and Violet publishing are separate gates, and a product must clear both.

## Reconciliation

Publication changes are delivered by webhook. If your integration needs to confirm the current state of a merchant's catalog (after downtime on your side, for example), re-read the catalog through the standard Violet catalog endpoints. The result is already scoped to the merchant's published set.

{% hint style="warning" %}
**Known limitation.** Violet's scheduled catalog validation checks whether products still exist and whether they are Draft or Archived in Shopify, but it does not currently re-verify publication membership. If a merchant unpublishes a product during an extended interruption in webhook delivery, that Offer can remain active in Violet until the product is next modified. If accurate withdrawal is critical to your integration, periodically reconcile against the catalog endpoints.
{% endhint %}

## Testing your integration

On a Shopify development store connected to your channel:

1. Publish a product to your channel and confirm you receive `OFFER_ADDED`.
2. Edit the product's title and confirm you receive `OFFER_UPDATED`.
3. Unpublish the product. Confirm you receive `OFFER_REMOVED` with `publishing_status: NOT_PUBLISHED`, and that your integration withdraws it. Note that `status` will still read `AVAILABLE`; the product is still selling in the merchant's store.
4. Re-publish it and confirm it returns with the same Offer id and `publishing_status: PUBLISHED`.
5. Confirm a product you have never published to the channel never reaches your catalog.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.violet.io/ecom-platforms/shopify/sales-channel/catalog-sync.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
