# WooCommerce & Shopify Price and Stock Change Monitor (`uniano/woocommerce-shopify-price-stock-monitor`) Actor

Monitor WooCommerce and Shopify stores for price, sale and stock changes, new and removed products. Outputs only the changes, with old and new values, ready for webhooks, n8n and Make.

- **URL**: https://apify.com/uniano/woocommerce-shopify-price-stock-monitor.md
- **Developed by:** [Uniano](https://apify.com/uniano) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 product checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## WooCommerce & Shopify Price and Stock Change Monitor

![Price & Stock Change Monitor: only what changed, with old and new value](https://api.apify.com/v2/key-value-stores/NAMUCI0cia3LG5rak/records/hero.png?signature=p0Yz60eiLwtPpk7GWnie)

Track competitor prices, sales and stock on **WooCommerce** and **Shopify** stores and get **only what changed** since the last run: old value, new value, product link, SKU and currency. No full catalogue dumps to diff yourself, no spreadsheets to compare.

Schedule it daily (or hourly) and send the changes to Slack, Google Sheets, n8n, Make or Zapier.

### Quick start

1. **Add store URLs.** Paste a store home page, a category or collection, or single product links. WooCommerce and Shopify are detected automatically.
2. **Run once.** The first run saves a baseline of every product, so the dataset shows one *Baseline saved* row per store.
3. **Schedule it.** Add a schedule (for example, daily at 06:00) with the same input. Every next run lists only what changed. Add a webhook to get the changes in n8n, Make, Zapier or a Slack message.

A second run looks like this:

![Example output: sale started, sold out, back in stock and price changes on the Shopify Dawn demo store](https://api.apify.com/v2/key-value-stores/NAMUCI0cia3LG5rak/records/example-changes.png?signature=18NEo44fnwisoPQOB5Iyc)

<sub>Example generated by the Actor's own code from the Shopify Dawn demo store's product feed. The five store edits between the two runs are simulated, because a demo store does not change by itself.</sub>

### What it detects

| Change type | Example |
|---|---|
| `price_changed` | 25.00 → 19.99 (−20.04 %) |
| `sale_started` / `sale_ended` | 8.00 → 6.00 when a sale starts |
| `sale_price_changed` / `regular_price_changed` | sale price or the crossed-out price changes during a sale |
| `stock_status_changed` | `out_of_stock` → `in_stock` (back in stock), `in_stock` → `backorder` |
| `stock_quantity_changed` | 3 → 1 (WooCommerce stores that show the remaining quantity) |
| `product_added` / `product_removed` | a new product appears, a product disappears |
| `currency_changed` | the store switched currency |

### Use cases

- **Competitor price tracking**: see every price move of your competitors the morning after it happens.
- **Stock alerts**: know when a competitor sells out (a chance to raise your price) or when a product is back in stock.
- **MAP monitoring**: watch your own SKUs at resellers' WooCommerce/Shopify stores and catch prices below the minimum advertised price. Use the SKU filter and "minimum price change".
- **New product launches**: get `product_added` for every new item in a competitor's catalogue or collection.

### How it works

1. You enter store URLs (whole catalogue), category/collection URLs, or single product URLs.
2. The Actor detects the platform and reads the store's **public product feed**:
   WooCommerce Store API (`/wp-json/wc/store/v1/products`) or Shopify `/products.json`.
   These are JSON endpoints that the platforms publish for storefronts; no login and no HTML scraping.
3. **First run = baseline.** The current state is saved in a named key-value store and the dataset gets one `summary` item per store, not hundreds of "new product" rows.
4. **Every next run** compares the store with the saved snapshot and writes only the changes, plus one `summary` per store.
5. Optionally, one JSON payload with all changes is POSTed to your webhook.

Schedule the Actor in Apify (Schedules → e.g. every day at 06:00). Keep the same input and the same "Snapshot store name" so each run compares with the previous one.

### Input

| Field | What it does |
|---|---|
| **Store or product URLs** | `https://store.com` (whole catalogue), `https://store.com/product-category/hoodies/`, `https://store.com/collections/sale`, `https://store.com/product/blue-hoodie/`, `https://store.com/products/blue-hoodie`. Mix stores and platforms freely. |
| Max products per store | Upper limit per store and run (default 1,000). Shopify variants count as products. |
| Categories / collections | Only check products in these WooCommerce categories (slug or ID) or Shopify collections (handle). |
| SKUs | Only check these SKUs (case-insensitive). |
| Change types to report | Leave empty for all. |
| Minimum price change (%) | Ignore small price moves, e.g. 2 %. |
| Webhook URL | Receives one JSON POST per run with all changes. |
| Report all products on the first run | Output every product as `product_added` on the first run instead of a silent baseline. |
| Include unchanged products | Also output the full current state of every product (`type: "unchanged"`). |
| Reset baseline | Start over with a new baseline. |
| Snapshot store name | Separate watchlists, e.g. `competitors-de` and `resellers-map`. |

Example input:

```json
{
  "startUrls": [
    { "url": "https://competitor-one.com" },
    { "url": "https://competitor-two.myshopify.com/collections/sale" },
    { "url": "https://reseller.com/product/blue-hoodie/" }
  ],
  "maxProductsPerStore": 2000,
  "minPriceChangePercent": 1,
  "webhookUrl": "https://your-n8n.example.com/webhook/price-changes"
}
```

### Output

Every dataset item has a `type`: `change`, `summary`, `error` (or `unchanged` if enabled). Two table views are ready in the Output tab:

- **Changes**: what changed, product, before and now (with currency), store and product link. Each store's block ends with its summary row.
- **Run summary**: the result per store and, if a store could not be read, what went wrong and how to fix it.

Every item also carries the same ready-to-show text fields for your own reports: `label` ("Price ↓ 20.04%", "Back in stock", "Baseline saved"), `productName`, `oldDisplay` / `newDisplay` ("25.00 EUR", "In stock"), `storeName`, `message` and `productUrl`. They are empty strings where they do not apply (for example, `productName` on a summary), never `null`; on summary and error items `productUrl` links to the store.

A change item:

```json
{
  "type": "change",
  "label": "Price ↓ 20.04%",
  "changeType": "price_changed",
  "store": "https://shop.example.com",
  "storeName": "shop.example.com",
  "platform": "woocommerce",
  "productId": "10",
  "variantId": null,
  "productName": "Blue Hoodie",
  "productUrl": "https://shop.example.com/product/blue-hoodie/",
  "sku": "HOOD-BLUE",
  "currency": "EUR",
  "field": "price",
  "oldValue": 25,
  "newValue": 19.99,
  "priceDiff": -5.01,
  "priceDiffPercent": -20.04,
  "direction": "down",
  "price": 19.99,
  "regularPrice": 19.99,
  "salePrice": null,
  "onSale": false,
  "stockStatus": "in_stock",
  "stockQuantity": null,
  "oldDisplay": "25.00 EUR",
  "newDisplay": "19.99 EUR",
  "message": "",
  "detectedAt": "2026-11-03T06:00:12.000Z",
  "previousCheckAt": "2026-11-02T06:00:09.000Z",
  "watchKey": "t_5b0c2f1e9a7d43c8b1e2f0aa"
}
```

A summary item (one per store and run):

```json
{
  "type": "summary",
  "label": "7 changes",
  "store": "https://shop.example.com",
  "storeName": "shop.example.com",
  "platform": "woocommerce",
  "platformName": "WooCommerce",
  "productName": "",
  "productUrl": "https://shop.example.com",
  "oldDisplay": "",
  "newDisplay": "",
  "status": "changes_detected",
  "isBaseline": false,
  "productsChecked": 7,
  "changesDetected": 7,
  "changesByType": { "price_changed": 1, "sale_ended": 1, "sale_started": 1, "stock_status_changed": 1, "stock_quantity_changed": 1, "product_added": 1, "product_removed": 1 },
  "capReached": false,
  "budgetReached": false,
  "errors": 0,
  "message": "7 changes since 2026-11-02 06:00 UTC (7 products checked)."
}
```

`status` is `baseline_created`, `changes_detected`, `no_changes` or `failed`. Stores that cannot be read produce an `error` item with a code (`NOT_A_SUPPORTED_STORE`, `API_BLOCKED`, `API_DISABLED`, `RATE_LIMITED`, `HTTP_ERROR`, `NETWORK_ERROR`, …) and a hint. The other stores in the run are processed normally.

### Webhooks, n8n, Make, Zapier

With a **Webhook URL**, the Actor POSTs once per run when something changed or a store or product could not be read (for example, a store started blocking requests). Turn on "Send webhook even without changes" to get it after every run:

```json
{
  "event": "store_change_monitor.run_finished",
  "schemaVersion": 1,
  "text": "Store Change Monitor: 3 changes in 2 stores.\n• Blue Hoodie (shop.example.com): Price ↓ 20.04%, 25.00 → 19.99 EUR\n…",
  "actorRunId": "…",
  "runUrl": "https://console.apify.com/actors/runs/…",
  "datasetUrl": "https://console.apify.com/storage/datasets/…",
  "checkedAt": "2026-11-03T06:00:00.000Z",
  "totals": { "stores": 2, "productsChecked": 340, "changes": 3, "errors": 0 },
  "summaries": [ … ],
  "errors": [ … ],
  "changes": [ … ],
  "changesTruncated": false
}
```

The `changes` items are the same as in the dataset (up to 500 per payload; the dataset always has all of them). `text` is a ready-made plain-text summary (up to 10 change lines and a link to the run) that you can drop into a Slack, Telegram, Teams or email step as is. `runUrl` and `datasetUrl` link to the run in Apify Console. `schemaVersion` changes only if the payload shape changes in an incompatible way.

- **n8n**: Webhook node → Slack node with `{{$json.body.text}}`, or "Split Out" on `changes` → Google Sheets / IF `changeType` = `price_changed` and `priceDiffPercent` < −10.
- **Make / Zapier**: "Custom webhook" trigger, then iterate over `changes`.
- You can also use Apify's own integrations (Slack, Google Drive, email) on the dataset.

### Pricing

Pay per event, you only pay for what is checked and found:

- **$0.50 per 1,000 products checked** (Shopify: per variant)
- **$1.00 per 1,000 changes detected**

Examples: a store with 500 products checked daily costs about 500 × 30 × $0.0005 = **$7.50 per month** plus changes. 10 changes a day add about $0.30 per month. The first (baseline) run is billed only for products checked.

Set a maximum cost per run in the run options. The Actor stops cleanly at your limit. If the limit cuts the change list, the snapshot is not advanced, so the remaining changes are reported in the next run.

### Limits (please read)

- **Only WooCommerce and Shopify** stores whose public product feed is enabled. Some stores disable the WooCommerce Store API or protect `/products.json`. You then get an `API_BLOCKED` or `API_DISABLED` error item, not wrong data.
- **No bypassing**: the Actor does not log in, use proxies to evade blocks, solve CAPTCHAs or scrape HTML. It identifies itself with an honest User-Agent and waits between requests.
- **Shopify stock** is in/out of stock only. Shopify's public feed does not expose quantities.
- **WooCommerce variable products** are tracked at product level (lowest variation price and overall stock status), not per variation.
- **WooCommerce quantities** appear only if the store shows them (e.g. "3 in stock" or low-stock notices).
- **Product cap**: when "Max products per store" is reached, removed products are not reported for that store, because the rest of the catalogue was not read.
- **0 products** returned by a store that had products before is treated as a temporary problem: nothing is reported as removed, and you get an `EMPTY_CATALOGUE` error item.
- Prices are what the public feed shows (usually the guest price incl./excl. tax as configured by the store). Customer-group or logged-in prices are not visible.

### FAQ

**Why didn't my first run output any products?** The first run saves the baseline: you see one *Baseline saved* row per store. Run it again or add a schedule, and the next run lists only the changes. Turn on "Report all products on the first run" or "Include unchanged products" if you need a full list.

**Can I monitor single products?** Yes. Use product links in the standard format: WooCommerce `/product/<slug>/` or `?p=<id>`, Shopify `/products/<handle>`. Links of the same store are checked together.

**Does it collect personal data?** No. Only product data (name, URL, SKU, prices, stock) is read. No customers, reviews or orders.

**Where is the snapshot?** In the named key-value store from the input (default `store-change-monitor-snapshots`), one record per store and filter set. Changing filters starts a new baseline for that combination.

Found a bug or a store that should work but doesn't? Please open an issue with the store URL.

# Actor input Schema

## `startUrls` (type: `array`):

Add one URL per row. The platform is detected automatically.<br>• Whole store: <code>https://store.com</code><br>• Category or collection: <code>/product-category/hoodies/</code>, <code>/collections/sale</code><br>• Single product: WooCommerce <code>/product/\<slug>/</code> or <code>?p=\<id></code>, Shopify <code>/products/\<handle></code>

## `maxProductsPerStore` (type: `integer`):

Upper limit of products checked (and billed) per store and run. Shopify variants count as products. When the limit is reached, removed products are not reported for that store.

## `categories` (type: `array`):

Only check products in these categories. WooCommerce: category slug or ID. Shopify: collection handle. Ignored for product URLs.

## `skus` (type: `array`):

Only check and bill products with these SKUs (case-insensitive). Useful for MAP monitoring of your own products at resellers.

## `changeTypes` (type: `array`):

Leave empty to report every change type.

## `minPriceChangePercent` (type: `number`):

Ignore price changes smaller than this percentage. 0 reports every change.

## `webhookUrl` (type: `string`):

Optional. One JSON POST per run with all changes and a ready-made <code>text</code> summary, for n8n, Make, Zapier or your own endpoint, e.g. <code>https://hooks.zapier.com/hooks/catch/…</code>. Sent when something changed or a store or product could not be read; with the option below, after every run.

## `webhookOnNoChanges` (type: `boolean`):

Also send the webhook when nothing changed and there were no problems. Useful as a daily heartbeat for your automation.

## `reportProductsOnFirstRun` (type: `boolean`):

By default the first run only saves a baseline. Turn on to output every product as "product_added" on the first run (billed as changes).

## `includeUnchanged` (type: `boolean`):

Also output an "unchanged" item with the current state of every checked product (not billed as changes).

## `resetBaseline` (type: `boolean`):

Ignore the saved snapshot and start a new baseline in this run.

## `platform` (type: `string`):

Leave on auto-detect. Set it only if detection picks the wrong feed for a store.

## `snapshotStoreName` (type: `string`):

Named key-value store that keeps the last state of each watched store between runs. Use different names for independent watchlists.

## `maxConcurrency` (type: `integer`):

How many stores are processed at the same time. Requests to one store are always sequential.

## `requestDelayMs` (type: `integer`):

Pause between two requests to the same store, to stay polite.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://theme-dawn-demo.myshopify.com"
    }
  ],
  "maxProductsPerStore": 50,
  "minPriceChangePercent": 0,
  "webhookOnNoChanges": false,
  "reportProductsOnFirstRun": false,
  "includeUnchanged": false,
  "resetBaseline": false,
  "platform": "auto",
  "snapshotStoreName": "store-change-monitor-snapshots",
  "maxConcurrency": 3,
  "requestDelayMs": 500
}
```

# Actor output Schema

## `changes` (type: `string`):

Price, sale and stock changes, new and removed products.

## `overview` (type: `string`):

One row per store: baseline saved, number of changes, or the problem and how to fix it.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://theme-dawn-demo.myshopify.com"
        }
    ],
    "maxProductsPerStore": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("uniano/woocommerce-shopify-price-stock-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [{ "url": "https://theme-dawn-demo.myshopify.com" }],
    "maxProductsPerStore": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("uniano/woocommerce-shopify-price-stock-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://theme-dawn-demo.myshopify.com"
    }
  ],
  "maxProductsPerStore": 50
}' |
apify call uniano/woocommerce-shopify-price-stock-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,uniano/woocommerce-shopify-price-stock-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/uBzbCe7lmyVih2D5d/builds/A5FSNxQ38bbqa9Egm/openapi.json
