Integration Guide
How to Monitor a Competitor's Shopify or WooCommerce Store
Catch a competitor's price changes and new product launches automatically, whether they run Shopify or WooCommerce — including the one field each platform actually publishes for a clean diff.
Written by Alex P.
- Shopify
- WooCommerce
- competitor monitoring
- price tracking
- ecommerce
“How do you track competitor retail prices?” comes up in r/ecommerce on a loop, and the honest answers cluster around the same complaint: checking product pages by hand doesn’t scale past a handful of SKUs, and most people’s first attempt is a general-purpose page-change watcher that fires on every layout tweak and banner swap, not just the number that actually matters.
The fix is narrower than a generic page monitor: read the store’s own product data directly, and diff on the one field each platform publishes specifically to say “this changed.” Shopify and WooCommerce structure that differently, so this is really two small pipelines that share one shape.
Shopify: diff on updatedAt
A Shopify storefront publishes its catalog as structured product data — no page-scraping guesswork about what changed:
const API_KEY = process.env.FETCHLAYER_API_KEY;
async function getShopifyProducts(store) {
const res = await fetch('https://api.fetchlayer.dev/shopify-stores/products', {
method: 'POST',
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ store, limit: 250, pages: 1 }),
});
const { products } = await res.json();
return products;
}
Each product carries priceMin/priceMax (the lowest and highest variant price), available (true when at least one variant can be bought), onSale (true when a variant’s compareAtPrice sits above its price), and updatedAt. That last field is the one to diff on — it changes whenever the store edits the product, price included, without you having to compare every field yourself:
function findShopifyChanges(previous, current) {
const prevById = new Map(previous.map((p) => [p.id, p]));
const changes = [];
for (const product of current) {
const prev = prevById.get(product.id);
if (!prev) {
changes.push({ type: 'new', product });
continue;
}
if (prev.updatedAt !== product.updatedAt) {
changes.push({
type: 'updated',
product,
priceChanged: prev.priceMin !== product.priceMin || prev.priceMax !== product.priceMax,
});
}
}
return changes;
}
Note the guard: updatedAt changing doesn’t always mean the price moved — a description edit or a new tag bumps it too. Check priceMin/priceMax explicitly before treating an update as a price change worth an alert.
WooCommerce: diff on dateModified
WooCommerce stores are self-hosted, so the same store can expose product data through one of three different sources depending on how it’s configured — and the source changes what you get back:
async function getWooProducts(store) {
const res = await fetch('https://api.fetchlayer.dev/woocommerce-stores/products', {
method: 'POST',
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ store, limit: 100 }),
});
const { products, dataSource } = await res.json();
return { products, dataSource };
}
dataSource tells you which of catalog-api, content-api, or product-pages actually answered — catalog-api carries every field including price and stock, content-api carries no price or SKU at all, and product-pages is read from rendered pages and costs more per product. If you’re building a price monitor and the store only offers content-api, prices won’t be there to diff — the response is honest about it, but your code should check dataSource before assuming a missing price is a bug.
Each product’s prices object (price, regularPrice, salePrice, onSale, all exact decimal strings, never floats) is what to compare, and dateModified is explicitly the field WooCommerce publishes for exactly this — “the field to diff for catalog monitoring”:
function findWooChanges(previous, current) {
const prevBySlug = new Map(previous.map((p) => [p.slug, p]));
const changes = [];
for (const product of current) {
const prev = prevBySlug.get(product.slug);
if (!prev) {
changes.push({ type: 'new', product });
continue;
}
if (prev.dateModified !== product.dateModified) {
changes.push({
type: 'updated',
product,
priceChanged: prev.prices?.price !== product.prices?.price,
});
}
}
return changes;
}
Running both from one watchlist
Most teams track competitors on whichever platform they actually run — you rarely need to guess. If you do need to check, detect-store on either platform answers cheaply before you commit to a full product pull:
const WATCHLIST = [
{ name: 'competitor-a', platform: 'shopify', store: 'competitor-a.com' },
{ name: 'competitor-b', platform: 'woocommerce', store: 'competitor-b.com' },
];
async function run() {
for (const { name, platform, store } of WATCHLIST) {
const products = platform === 'shopify'
? await getShopifyProducts(store)
: (await getWooProducts(store)).products;
// ...load previous snapshot, diff with the matching function above,
// save the new snapshot, alert on changes.
console.log(`${name}: ${products.length} products read`);
}
}
Keep one JSON snapshot file per competitor rather than one shared file — a store temporarily returning fewer products (a filter glitch, a partial page walk) shouldn’t corrupt every other competitor’s baseline in a shared diff.
New products vs. restocks
A product missing from yesterday’s pull and present today is new stock, not necessarily a new SKU — WooCommerce and Shopify both keep a product live through out-of-stock periods rather than removing the listing. Check available (Shopify) or stockStatus (WooCommerce, one of in-stock, out-of-stock, on-backorder, or unknown) alongside presence in the listing before calling something a genuine launch:
function isGenuineNewProduct(product, platform) {
if (platform === 'shopify') return product.available;
return product.stockStatus === 'in-stock';
}
Practical notes
- A Shopify product’s variant list caps at 250.
variantsTruncated: truesays when a product has more than that — rare outside stores with heavy size/color matrices, but worth checking before assuming you’ve priced every option. - WooCommerce’s
attributesandvariationsare absent on theproduct-pagessource. If a monitored store only answers from rendered pages, you get title, price, and stock — not the full variation matrix. CheckdataSourcerather than treating a thin response as an error. - Shopify’s
productTypeandvendorfields are the store’s own taxonomy, not a fixed list. Two competitors selling the same category of product will label it differently — group by your own category mapping, not by matching these strings across stores.
Next Steps
- Shopify Store API · WooCommerce Store API
- Get a free API key — no credit card required