# Create or update products

This guide shows you how to consume [ProductUpsertEvent](/content/product/connect-channel-platform/webhooks/webhook/webhooks/productupsertevent) events end to end:

1. Receive the event.
2. Recognize whether the product belongs to a group of related products.
3. Map the transformed attributes and the product type onto your channel's product model.
4. Create or update the product.
5. Report the product status back, so that the offer waiting on it can go live.


## Business context

A **product** describes what is sold.
An **offer** is a seller's sellable proposition for that product, and it cannot go live before the product exists on the channel.
For most sellers, nothing has to be created: the product is already in the channel's catalog, and the offer matches it by its identifiers.
This event covers the remaining case, where the product is not on the channel and the channel requires it to exist before an offer can attach to it.

Correct product creation is what unblocks everything downstream.
A product that stays outside the channel's catalog is an offer that can never be created.
It is a seller who waits for a listing that will not appear.

The channel usually has the last word on whether a product is acceptable, sometimes immediately and sometimes after a review.
The status and the diagnostics you report here are what turn a rejected product into something the seller can fix.

Read the [Catalog flow](/content/product/connect-channel-platform/developer-guide/catalog-flow) first for the definitions this guide builds on: product against offer, the lifecycle states, and the feedback loop.
[Taxonomy](/content/product/connect-channel-platform/developer-guide/taxonomy) covers the product types that this event's `product_type_id` refers to.

## When to use this

Handle [ProductUpsertEvent](/content/product/connect-channel-platform/webhooks/webhook/webhooks/productupsertevent) when Mirakl Connect asks you to **create or correct a product** on the channel.
Mirakl Connect sends it while the product is **not usable on the channel**, which means while the `product_status` you last reported is `PRODUCT_DOES_NOT_EXIST`, `PRODUCT_PENDING_APPROVAL`, or a product-level `ACTION_REQUIRED`.
This event also carries every edit of a product, so you can receive it again for a product you already reported as `PRODUCT_CREATED`.
Read [Editing an active product](#editing-an-active-product) below.
It is never sent for a product you reported as `PRODUCT_REFUSED`.

Four properties decide whether you receive it at all:

- **It requires a registered taxonomy.**
Mirakl Connect sends a product only after it transforms the product against the channel's product types, so the [taxonomy](/content/product/connect-channel-platform/developer-guide/taxonomy) must be registered first.
The `product_type_id` and every attribute in the event arrive already expressed in the channel's own vocabulary.
- **Mirakl gates it, for the channel.**
Registering the taxonomy is not enough on its own.
Mirakl must activate that taxonomy in the Catalog Transformer and train the model on it, and that step is not self service.
Read [Activation by Mirakl](/content/product/connect-channel-platform/developer-guide/taxonomy#activation-by-mirakl).
- **Catalog Transformer gates it, for each seller.**
Read [Product creation requires Catalog Transformer](#product-creation-requires-catalog-transformer) below.
For sellers without the capability, Mirakl Connect never sends this event, whatever you report as their product status.
- **Attempts are periodic, not instant.**
Mirakl Connect sweeps for products that need creation, and for active products that need an edit, on its own cadence.
It re-sends a product when that product's transformed data changes, so a corrected product comes back to you and the seller does not have to do anything else.


The **product status alone** drives this event, so it can arrive for an item whose offer is already live.
A product under review, or one you reported as action-required, keeps receiving product events while its offer keeps selling.

**When not to use this.**
This event does not create the offer.
It carries the price, the stock, and the offer's attributes alongside the product data, so a channel that creates both in a single operation can do so here.
Read [step 4](#4-map-the-product-to-the-channel).
Mirakl Connect still tracks the offer separately, and only `offer_status: OFFER_DOES_NOT_EXIST` asks it for an offer creation.
To build an offer against a product that already exists, read [Create and update offers](/content/product/connect-channel-platform/developer-guide/catalog/create-and-update-offers).
For the steady-state price and stock of a live offer, read [Sync price and stock](/content/product/connect-channel-platform/developer-guide/catalog/sync-price-and-stock).

### Product creation requires Catalog Transformer

[ProductUpsertEvent](/content/product/connect-channel-platform/webhooks/webhook/webhooks/productupsertevent) is the only catalog event that Mirakl Connect gates on a paid capability.
Mirakl Connect emits it only for sellers who have **Catalog Transformer** enabled.
For every other seller, Mirakl Connect never attempts product creation, and an offer can only go live only against a product that already exists on the channel.

**The seller's Mirakl Connect plan** decides whether that seller has the capability.
Nothing that the channel or your connector configures decides it:

- **Sellers on the free plan do not have Catalog Transformer.**
They cannot push a new product to a channel, so their offers can only attach to products already in the channel's catalog.
Your connector never receives a [ProductUpsertEvent](/content/product/connect-channel-platform/webhooks/webhook/webhooks/productupsertevent) for them.
- **Sellers on a paid plan have Catalog Transformer.**
Mirakl Connect transforms their products into channel products against the channel's registered [taxonomy](/content/product/connect-channel-platform/developer-guide/taxonomy).
Reporting `PRODUCT_DOES_NOT_EXIST` for one of their items is what triggers the event that creates it.


Expect stores whose items never reach the product creation stage, and treat that as normal rather than as a stalled synchronization.

### Editing an active product

Mirakl Connect can ask you to update a product that is already `PRODUCT_CREATED` on the channel.
It sends [ProductUpsertEvent](/content/product/connect-channel-platform/webhooks/webhook/webhooks/productupsertevent) again for that product when its transformed data changes.
Handle it the same way as a first creation, and report `product_status: PRODUCT_CREATED` again once the channel applies the update.

Every channel has this behavior today, except the channels Mirakl operates itself.
This restriction is temporary.
For a channel without it, `PRODUCT_CREATED` stops the event.

Editing an active product still requires Catalog Transformer for the seller.
Read [Product creation requires Catalog Transformer](#product-creation-requires-catalog-transformer) above.

## How it works

Mirakl Connect emits one event for each product.
A transport delivers it.
Your connector validates it, deduplicates it, assembles any group it belongs to, and creates the product on the channel.
You then report the product's status.

Whether the channel accepts a product at once or reviews it first is the channel's behaviour.
That behaviour decides how many times you report for one product.

```mermaid
sequenceDiagram
    box rgb(219,234,254) Mirakl
    participant P as Mirakl Connect
    end
    box rgb(209,250,229) Middleware
    participant T as Transport
    participant I as Channel Connector
    end
    box rgb(254,240,199) Channel
    participant C as Channel
    end

    Note over P: A product needs creation, or an active product needs an edit
    P--)T: ProductUpsertEvent (one per product)
    T--)I: Deliver event (at-least-once, ordered per product_id)
    I->>I: Validate, deduplicate, assemble the group
    I->>C: Create or update the product (product type + transformed attributes)

    alt The channel creates it outright
        C-->>I: Product created
        I->>P: updateStoreCatalogItems (PRODUCT_CREATED + OFFER_DOES_NOT_EXIST)
        Note over P,I: The offer creation follows
    else The channel reviews it first
        C-->>I: Submitted for review
        I->>P: updateStoreCatalogItems (PRODUCT_PENDING_APPROVAL)
        C-->>I: Approved, or refused with a reason
        I->>P: updateStoreCatalogItems (PRODUCT_CREATED, or PRODUCT_REFUSED + diagnostics)
    else The product cannot be built
        C-->>I: Rejected, a required attribute is missing or invalid
        I->>P: updateStoreCatalogItems (ACTION_REQUIRED + product_diagnostics)
    end
```

Two properties of this flow shape everything below:

- **Delivery is at-least-once, and ordered only for each `product_id`.**
The same event can arrive twice, and events for different products can overtake one another.
- **A product's outcome is not always known when you submit it.**
A channel that reviews products returns its verdict later, which makes the reporting iterative rather than a single call.


## Step by step

### 1. Receive the event

You receive `PRODUCT_UPSERT` on the queue or the topic your subscription delivers to: AWS SQS, Google Pub/Sub, or Azure Service Bus.
The body is identical on all three.
For how to set one up, read [Receiving events](/content/product/connect-channel-platform/developer-guide/receiving-events).

```json
{
  "id": "01jt31mw7wy3x4zs55kawgg1xe",
  "time": "2024-05-20T10:15:30.500Z",
  "type": "PRODUCT_UPSERT",
  "source": "01jv2a4c8xk3m9p1r5t7y2w4qs",
  "sequenceType": "PROGRESS",
  "sequence": 1,
  "sequenceSize": 3,
  "data": {
    "channel_id": "001",
    "store_id": "2005",
    "product_id": "SKU_123456",
    "product_type_id": "Cell Phones",
    "attributes": [
      { "id": "product_name", "value": "iPhone 14" },
      { "id": "description", "value": "Smartphone 128 GB", "locale": "en_GB" },
      { "id": "description", "value": "Smartphone 128 Go", "locale": "fr_FR" },
      { "id": "storage", "value": "128 GB" },
      { "id": "color", "value": "red" }
    ],
    "stock": {
      "action_type": "UPDATE",
      "breakdown": [
        { "type": "SELLER_WAREHOUSE", "warehouse": { "id": "WH_PARIS" }, "quantity": 500 }
      ]
    },
    "inventory": {
      "action_type": "UPDATE",
      "quantity": 500,
      "delivery_partner": { "name": "__self__" }
    },
    "standard_price": {
      "action_type": "UPDATE",
      "price": { "currency": "EUR", "amount": 899.99 }
    },
    "discount_price": {
      "action_type": "UPDATE",
      "price": { "currency": "EUR", "amount": 859.99 },
      "start_date": "2024-05-20T10:15:30.500Z",
      "end_date": "2024-06-20T10:15:30.500Z"
    },
    "offer_attributes": [
      { "type": "TEXT", "id": "label", "value": "iPhone 14 - Good Condition" }
    ],
    "identifiers": [
      { "type": "GTIN", "value": "1234567890123" }
    ]
  }
}
```

Key fields in `data`:

- **`channel_id`** and **`store_id`**: the channel and the seller store this product belongs to.
- **`product_id`**: the seller's product reference, also called the SKU.
It is the **partition key** for the ordering, and it is the identifier you report feedback against (step 5).
- **`product_type_id`**: the product type **in the channel's format**, resolved from the [taxonomy](/content/product/connect-channel-platform/developer-guide/taxonomy) you registered.
- **`attributes`**: the product's descriptive attributes, already transformed into the channel's format (step 4).
- **`stock`**, **`inventory`**, **`standard_price`**, **`discount_price`**, **`offer_attributes`**, and **`identifiers`**: the offer data carried alongside the product, for channels that create a product and its offer together.
Build against `stock`, which holds the quantity for each location.
`inventory` is the legacy aggregated form, kept for backward compatibility.


The envelope carries four fields that appear on **no other catalog event**: `source`, `sequenceType`, `sequence`, and `sequenceSize`.
They describe the group this product belongs to, and step 3 covers them.

Acknowledge the message on your queue once you have stored the event.
A message you leave unacknowledged returns to the queue, and you receive it again.
An acknowledgement is **not** a statement that the product was created.
You report that separately in step 5.
Read [Acknowledging a message](/content/product/connect-channel-platform/developer-guide/receiving-events#acknowledging-a-message).

### 2. Validate and deduplicate on the envelope `id`

Delivery is **at-least-once**, so the same event arrives more than once from time to time.
Before you act, deduplicate on the envelope `id`, a ULID that identifies the event.
Make a repeat delivery a no-op.

Then validate that you can build a product from the payload.
Do not blindly retry a product type your channel does not recognize, or a required channel attribute that the transformation did not supply.
**Report** it as `ACTION_REQUIRED` with a diagnostic in step 5, so the seller can correct the data and Mirakl Connect can send the product again.

Events that share a `product_id` are delivered **in order** relative to one another.
There is **no ordering guarantee across different products**.
Keep the processing of one `product_id` sequential, and process different products concurrently.

### 3. Handle grouped products

Some channels cannot accept a product on its own.
They need every variant of a garment, or every product of a brand, in one submission.
To support that, a channel can ask Mirakl Connect to **group related products**, and the envelope announces the grouping:

- **`source`**: the group's identifier.
Every product in the same group carries the same value.
- **`sequence`**: this product's position in the group, counting from `1`.
- **`sequenceSize`**: how many products the group contains in total.
- **`sequenceType`**: always `PROGRESS`.


Each channel configures the grouping on one of two attributes:

- **`VARIANT_GROUP_CODE`**: every product that shares a variant group code travels together, such as a garment's sizes and colours.
- **`BRAND`**: every product of the same brand in that store travels together.


When a channel does not group products, the four envelope fields **carry no group** and each event stands alone.
Treat a `source` that is missing or empty as a product on its own.

When the channel does group products, note two behaviours:

- **A group can contain a single product.**
A product with no value for the grouping attribute, or the only product of its kind in the store, arrives as `sequence: 1` of `sequenceSize: 1`.
Treat a group of one as the ordinary case, not as a special one.
- **Mirakl Connect assembles the group from the whole store, not from what is eligible.**
As soon as one product in a group needs creation, Mirakl Connect pulls in every other live product that shares that grouping value, including products it would not have sent on their own.
Expect events for products you have already created.


**A group is not guaranteed to arrive complete.**
A product can be deleted between the moment Mirakl Connect counts a group and the moment it sends the group, so you can receive fewer events than `sequenceSize` promises.
Handling an unfinished group is the connector's responsibility.

Never make the creation of a product depend on the completion of its group.
Set a bound on how long you wait for the remaining members, then submit what you have and report each product you received.
A connector that blocks until `sequence` reaches `sequenceSize` waits forever on any group that lost a member.

### 4. Map the product to the channel

Translate the event into your channel's product model.
Two fields carry the work, and both arrive **already expressed in the channel's own vocabulary**, because the transformation against your registered taxonomy happened before Mirakl Connect sent the event:

- **`product_type_id`** is the channel's product type identifier, not a Mirakl Connect category.
Use it as it is.
- **`attributes`** is a flat list of `{ id, value, locale }`.
The `id` is the attribute identifier on the channel, and the `value` is the value in the format the channel expects.


Do not derive or map again what the transformation already resolved.
Your work is to carry the attributes onto the channel, and to validate that the attributes the channel requires are present.
A required attribute that the transformation could not supply is the most common reason a product cannot be built, and to report it in step 5 is what gets it fixed.

**The same attribute `id` can appear more than once, once for each locale.**
Mirakl Connect sends a localized attribute as one entry for each locale, each entry with the same `id` and a different `locale`.
The `description` attribute in the [example above](#1-receive-the-event) shows this.
The `locale` property is present only on localized attributes, in the format `<ISO-639>` or `<ISO-639>_<ISO-3166>`.

Key the attributes by `id` **and** `locale`.
A connector that collapses the list into a map keyed on `id` alone keeps one arbitrary translation and silently drops the others.

The event also carries the offer's `stock`, `inventory`, prices, `offer_attributes`, and `identifiers`.
Use them here if your channel creates a product and its offer in one operation.
Each price and stock field carries its own `action_type`, exactly as on the other catalog events:

- `UPDATE` sets the value.
- `DELETE` clears it.
- `IGNORE` leaves it unchanged.


### 5. Report the product status

Once you have acted, report the outcome for each store catalog item with the [updateStoreCatalogItems](/content/product/connect-channel-platform/rest/connect/openapi3/product-feedback/updatestorecatalogitems) operation.
This is what advances the lifecycle, because Mirakl Connect acts on the `product_status` you report.

```
POST https://miraklconnect.com/api/channel-platform/v1/channel-catalog/001/store-catalog-items/2005
```

Report:

- **`product_status`**:
  - `PRODUCT_CREATED` once the product exists on the channel.
  - `PRODUCT_PENDING_APPROVAL` while the channel reviews it.
  - `PRODUCT_REFUSED` if the channel refused it.
  - `ACTION_REQUIRED` if something must be fixed before the channel can create it.
There is **no pending value** for work you have submitted but have no outcome for.
Leave the item out of the call, and report it once its result lands.
- **`product_diagnostics`**: the product's currently active problems.
Each one is a `message` that a human can read, with an optional `channel_attribute_id` that points at the attribute at fault.
The list is **full-replace**: send the complete set every time, and send an empty list to clear it.


**Report `offer_status: OFFER_DOES_NOT_EXIST` in the same call as `PRODUCT_CREATED`.**
Creating the product does not ask for an offer.
`PRODUCT_CREATED` only releases an offer that was **already** waiting for its product.
If you never reported the offer status, the item lands in a state where price and stock events keep flowing and **no offer is ever created**.
The connector looks healthy, and the seller's listing never appears.

There is one exception.
If you reported `PRODUCT_DOES_NOT_EXIST` on an earlier pass, that parked the offer as waiting, and `PRODUCT_CREATED` alone does release it.
Reporting both statuses together is correct either way.
Read [What your report triggers](/content/product/connect-channel-platform/developer-guide/catalog/report-feedback#what-your-report-triggers).

Report a created product whose offer is still missing like this:

```json
{
  "store_catalog_items": [
    {
      "id": "SKU_123456",
      "product_status": "PRODUCT_CREATED",
      "product_diagnostics": [],
      "offer_status": "OFFER_DOES_NOT_EXIST",
      "offer_diagnostics": []
    }
  ]
}
```

The operation is asynchronous (`202 Accepted`) and applies to each item separately, so one blocked product does not affect the other items in the same request.
For how to batch feedback across many items, read [Report feedback](/content/product/connect-channel-platform/developer-guide/catalog/report-feedback).

### 6. Follow a review to its outcome

On channels that review products before they publish them, to submit the product is not the end of the work.
Report `PRODUCT_PENDING_APPROVAL` when the product goes into review, then report again when the verdict arrives:

- **Approved**: report `PRODUCT_CREATED`, together with the offer status, as in step 5.
- **Refused**: report `PRODUCT_REFUSED` with a `product_diagnostics` message that explains why.
This status is **terminal**.
Mirakl Connect stops the creation attempts and does not retry on its own, and the offer status stays frozen at its previous value.
The seller sees the product as rejected, with your diagnostic as the reason.


While a product is `PRODUCT_PENDING_APPROVAL`, Mirakl Connect sends it again if its data changes, and otherwise waits for you.
It leaves the offer status untouched, so an offer you already reported as active keeps selling and keeps receiving price and stock updates.

## Complete example

Three products of one variant group, created together.
The channel groups products by `VARIANT_GROUP_CODE`, and the seller selected three sizes of the same shirt for store `2005` on channel `001`.

**1. The group arrives.**
Three events share the same `source`, and each one is numbered within the group.
The first event:

```json
{
  "id": "01jt31mw7wy3x4zs55kawgg1xe",
  "time": "2024-05-20T10:15:30.500Z",
  "type": "PRODUCT_UPSERT",
  "source": "01jv2a4c8xk3m9p1r5t7y2w4qs",
  "sequenceType": "PROGRESS",
  "sequence": 1,
  "sequenceSize": 3,
  "data": {
    "channel_id": "001",
    "store_id": "2005",
    "product_id": "SHIRT_BLUE_S",
    "product_type_id": "Shirts",
    "attributes": [
      { "id": "product_name", "value": "Oxford Shirt" },
      { "id": "size", "value": "S" },
      { "id": "color", "value": "blue" },
      { "id": "variant_group", "value": "OXFORD_BLUE" }
    ],
    "stock": {
      "action_type": "UPDATE",
      "breakdown": [
        { "type": "SELLER_WAREHOUSE", "warehouse": { "id": "WH_PARIS" }, "quantity": 40 }
      ]
    },
    "inventory": { "action_type": "UPDATE", "quantity": 40 },
    "standard_price": {
      "action_type": "UPDATE",
      "price": { "currency": "EUR", "amount": 59.99 }
    },
    "discount_price": { "action_type": "IGNORE" },
    "offer_attributes": [
      { "type": "TEXT", "id": "condition", "value": "NEW" }
    ],
    "identifiers": [
      { "type": "GTIN", "value": "1234567890123" }
    ]
  }
}
```

The other two events have the same shape.
They carry `SHIRT_BLUE_M` at `sequence: 2` and `SHIRT_BLUE_L` at `sequence: 3`, with the same `source`.

**2. Your connector acts.**
In order:

- Acknowledge each message on the queue.
- Check each envelope `id` against your deduplication store.
All three are new, so record them and continue.
- Recognize the shared `source` and buffer the three products as one group.
`sequence: 3` of `sequenceSize: 3` completes it, under a bound you control, so a member that never arrives cannot stall the other two.
- Map each product: `Shirts` as the channel's product type, the four attributes as they are, and the price and the stock carried alongside them.
- Submit the three products to the channel as one variant group.
The channel accepts them and publishes all three.


**3. You report the outcome.**
One call covers all three products.
Each entry reports the product as created **and** the offer as still missing:

```
POST https://miraklconnect.com/api/channel-platform/v1/channel-catalog/001/store-catalog-items/2005
```

```json
{
  "store_catalog_items": [
    { "id": "SHIRT_BLUE_S", "product_status": "PRODUCT_CREATED", "product_diagnostics": [], "offer_status": "OFFER_DOES_NOT_EXIST", "offer_diagnostics": [] },
    { "id": "SHIRT_BLUE_M", "product_status": "PRODUCT_CREATED", "product_diagnostics": [], "offer_status": "OFFER_DOES_NOT_EXIST", "offer_diagnostics": [] },
    { "id": "SHIRT_BLUE_L", "product_status": "PRODUCT_CREATED", "product_diagnostics": [], "offer_status": "OFFER_DOES_NOT_EXIST", "offer_diagnostics": [] }
  ]
}
```

**4. The lifecycle advances.**
Mirakl Connect records all three products as created and all three offers as missing.
It sends an [OfferUpsertEvent](/content/product/connect-channel-platform/webhooks/webhook/webhooks/offerupsertevent) with `use_case: OFFER_CREATION` for each product, which you handle with [Create and update offers](/content/product/connect-channel-platform/developer-guide/catalog/create-and-update-offers).
No further product event arrives for these three products.

If the channel had refused one shirt for a missing required attribute, that item alone would carry `ACTION_REQUIRED` with a diagnostic that names the attribute.
You would still report the other two as created in the same call.

## Common mistakes

### Reporting `PRODUCT_CREATED` without declaring the offer missing

**What it looks like:** the connector creates the product on the channel, reports `product_status: PRODUCT_CREATED`, and waits for an offer event to build the offer.

**The failure:** no offer event ever arrives.
`PRODUCT_CREATED` unblocks an offer that was already waiting for its product.
It does not put an offer into that state, and it is not a request to create one.
Because price and stock events keep flowing for the item, nothing looks broken, and the seller sees an offer that never appears.

**The fix:** report `offer_status: OFFER_DOES_NOT_EXIST` in the same call as the product status.
It is the only status that asks Mirakl Connect for a creation attempt.
Treat "the product now exists" and "the offer is still missing" as two facts that you must both state.

### Blocking until a product group completes

**What it looks like:** the connector buffers a group and submits it only when it has received `sequenceSize` events for that `source`, with no bound on the wait.

**The failure:** a group whose member was deleted between the counting and the sending never reaches its promised size, so the connector never flushes the buffer.
Every product in the group stays uncreated, and because the connector never submitted the products, there is no status to report.
The items simply go quiet, and nothing marks them as failed.

**The fix:** set a bound on the wait.
Submit what you have when the bound expires, report each product you received, and treat an incomplete group as an expected condition rather than as an error.
Mirakl Connect sends a product again when its data changes, so a member that was genuinely missing comes back.

### Expecting the product event to arrive first

**What it looks like:** the connector assumes that the first event it sees for a new item is a product event, and it treats a price and stock event for an unknown product as an error or as an out-of-order delivery.

**The failure:** Mirakl Connect enters the workflow as late as it can.
The first event for a newly selected product is normally a [PriceStockUpsertEvent](/content/product/connect-channel-platform/webhooks/webhook/webhooks/pricestockupsertevent), sent on the assumption that the offer is already live.
A connector that rejects it stalls before it starts.
So does a connector that waits for a product event, because that event does not come until the connector reports the product as missing.

**The fix:** treat the price and stock event as the entry point, and answer it with the truth about the item.
Report `PRODUCT_DOES_NOT_EXIST` when the product is absent, which is what asks for the product event described here.
Read [How synchronization begins](/content/product/connect-channel-platform/developer-guide/catalog-flow#how-synchronization-begins).

### Collapsing localized attributes into one value for each `id`

**What it looks like:** the handler reads `attributes` into a map keyed on `id`, so each later entry overwrites the earlier one.

**The failure:** a product localized into several languages keeps whichever translation came last and loses the others.
The connector creates the product with an arbitrary locale's text.
The channel can reject it outright, or publish it in the wrong language for its market.
Nothing in the payload looked malformed, so no error is reported anywhere.

**The fix:** key the attributes by `id` **and** `locale`.
Entries without a `locale` are not localized and appear once.
Localized entries repeat the same `id` for every locale the seller provides.

### Retrying a product the channel refused

**What it looks like:** after it reports `PRODUCT_REFUSED`, the connector keeps submitting the product to the channel, or reports the status again on a schedule and expects Mirakl Connect to retry.

**The failure:** `PRODUCT_REFUSED` is terminal by design.
Mirakl Connect stops the creation attempts and freezes the offer status, so nothing you report moves the item on its own.
Submitting the product again spends channel quota on a product the channel has already ruled on.

**The fix:** report `PRODUCT_REFUSED` once, with a `product_diagnostics` message that tells the seller why the channel refused the product.
A change to the product's data by the seller is what produces a fresh event.
Until then, there is nothing to retry.

## Related

- **[Catalog flow](/content/product/connect-channel-platform/developer-guide/catalog-flow)**: product against offer, how synchronization begins, and which event carries which stage.
- **[Create and update offers](/content/product/connect-channel-platform/developer-guide/catalog/create-and-update-offers)**: how to build the offer once its product exists on the channel.
- **[Report feedback](/content/product/connect-channel-platform/developer-guide/catalog/report-feedback)**: every status value, the event each pair of statuses triggers, and how to batch the export and report the statuses and diagnostics at scale.
- **[Taxonomy](/content/product/connect-channel-platform/developer-guide/taxonomy)**: how to register the product types that this event's `product_type_id` refers to.
- **[Receiving events](/content/product/connect-channel-platform/developer-guide/receiving-events)**: the transports, the ordering, and the delivery behaviour behind these events.