How to Detect WooCommerce on Any Website (API Guide)
WooCommerce is not a platform you can spot from the outside the way you can spot a hosted storefront. It is a plugin bolted onto a WordPress install that runs on whatever hosting the merchant already had, at whatever domain they already owned. There is no dedicated CDN hostname, no vendor DNS record, no checkout domain that gives it away. From the URL alone, a WooCommerce store and a static brochure site look identical.
What WooCommerce does leave behind is a set of specific artifacts in the HTML it renders. This guide covers what those are, why one carries a usable version number, how to query them through the DetectZeStack API, and the one failure mode that will quietly corrupt your data if you do not guard against it. Every API response below comes from a live scan run while writing this article.
What Makes WooCommerce Detectable
The wp-content/plugins/woocommerce Asset Path
WooCommerce enqueues its frontend JavaScript through the standard WordPress asset pipeline, which means the script is served from the plugin's own directory. Every store running it loads at least one file from a path shaped like this:
<script src="https://example.com/wp-content/plugins/woocommerce/assets/js/frontend/woocommerce.min.js?ver=9.3.6"></script>
Two things are happening in that one line. The path segment /wp-content/plugins/woocommerce/ is the detection: nothing else puts a directory by that name in an asset URL. And the ?ver= query string is WordPress's cache-busting parameter, which it populates with the version the plugin declares. That is where the version number in the API response comes from.
This is a real fetch of yithemes.com, grepped for WooCommerce asset references:
curl -sL -A "Mozilla/5.0" https://yithemes.com/ \
| grep -oE 'woocommerce/assets/js/[^"]+'
woocommerce/assets/js/frontend/woocommerce.min.js?ver=9.3.6
woocommerce/assets/js/js-cookie/js.cookie.min.js?ver=2.1.4
woocommerce/assets/js/select2/select2.full.min.js?ver=4.0.3
The first line is the one that matters. The bundled js-cookie and select2 versions are those libraries' own, not WooCommerce's — worth knowing if you ever parse these paths by hand and wonder why you got three different numbers.
woocommerce_* Cookies and body Classes
Two more signals show up on a store page, and they are useful for manual verification even though they are not what the API keys off.
The first is the woocommerce-no-js class, which WooCommerce adds to the body element and then strips with JavaScript on load. It sits alongside whatever other classes the theme emits. From the same live fetch of yithemes.com:
<body class="home page-template-default page page-id-22179 wp-embed-responsive
theme-yithemes woocommerce-no-js ywcas-yithemes-theme yith-wcstripe
yith-wcan-pro eur site-lang-en-US yith-not-stripe-customer safari">
The second is cookies. Once a visitor interacts with a cart, WooCommerce sets woocommerce_cart_hash, woocommerce_items_in_cart, and a wp_woocommerce_session_ cookie keyed by a hash. You can see these in Chrome DevTools under Application > Cookies. They are strong confirmation when present, but they are behavioural — a first, uninteracted page load usually will not set them, so their absence proves nothing.
The WordPress Dependency Signal
WooCommerce cannot exist without WordPress, which makes the pairing itself a check on your data. Every genuine WooCommerce detection should come back alongside a WordPress detection. In the API response, WooCommerce is filed under two categories at once — Ecommerce and WordPress plugins — which reflects exactly this dual nature.
The inverse is not true and is the more useful fact for prospecting: most WordPress sites are not stores. Live scans of wpbeginner.com and kinsta.com both return WordPress and an empty Ecommerce category. "Runs WordPress" is a population of hundreds of millions; "runs WooCommerce" is the commercial slice of it.
Manual Ways to Detect WooCommerce (and Where They Break)
View Source and DevTools
For a single domain, the manual check takes about fifteen seconds. Open the site, press Ctrl+U (or Cmd+Option+U on macOS), and search the source for plugins/woocommerce. If you get a hit, the site runs WooCommerce and the ?ver= on that line is your version number. Failing that, search the body tag for woocommerce-no-js, or open DevTools and look for the cart cookies after adding something to a cart.
From a terminal it is a one-liner:
curl -sL -A "Mozilla/5.0" https://example.com/ | grep -c "plugins/woocommerce"
Why Manual Checks Do Not Scale
That workflow is fine once and useless at a hundred domains, for reasons that have nothing to do with typing speed:
- You get a bit, not a record. A grep hit tells you WooCommerce is present. It does not tell you the hosting, the payment stack, the SEO plugin, or the WordPress version — the context that decides whether the store is worth contacting.
- A grep miss is ambiguous.
curlreturning nothing looks exactly the same whether the site is not a store, blocked your request, or served a redirect. You cannot tell those apart from a count of zero. - Redirects, encodings, and bot walls. Handling
wwwcanonicalisation, gzip, HTTPS hops, and challenge pages correctly is a small project of its own, and every shortcut shows up later as a false negative.
Detect WooCommerce With the DetectZeStack API
Try It Free With GET /demo
The /demo endpoint runs a full scan with no API key and no signup, so start there. Here is a real scan of woocommerce.com itself, filtered to the one detection:
curl -s "https://detectzestack.com/demo?url=woocommerce.com" \
| jq '.technologies[] | select(.name == "WooCommerce")'
{
"name": "WooCommerce",
"version": "11.1.0",
"categories": [
"Ecommerce",
"WordPress plugins"
],
"confidence": 100,
"description": "WooCommerce is an open-source ecommerce plugin for WordPress.",
"website": "https://woocommerce.com",
"icon": "WooCommerce.svg",
"source": "http"
}
Read the fields. confidence is 100 because the asset path matched directly rather than being inferred. source is http, meaning the evidence came from the HTML response — and for WooCommerce it will always be http, which turns out to matter a great deal later in this article. version is populated straight from the script's ?ver= string.
Full Stack Scan With GET /analyze
The boolean is rarely the interesting part. The full /analyze response is what turns a domain into a qualified record, because it returns everything else on the page. Here is the same woocommerce.com scan through the authenticated endpoint, trimmed to the fields that matter:
curl -s "https://detectzestack.p.rapidapi.com/analyze?url=woocommerce.com" \
-H "X-RapidAPI-Key: YOUR_KEY" \
-H "X-RapidAPI-Host: detectzestack.p.rapidapi.com" | jq '.'
{
"url": "https://woocommerce.com",
"domain": "woocommerce.com",
"technologies": [
{ "name": "WooCommerce", "categories": ["Ecommerce", "WordPress plugins"], "confidence": 100, "version": "11.1.0", "source": "http" },
{ "name": "WordPress", "categories": ["CMS", "Blogs"], "confidence": 100, "version": "7.0.2", "source": "http" },
{ "name": "WordPress VIP", "categories": ["PaaS"], "confidence": 100, "version": "", "source": "http" },
{ "name": "Yoast SEO", "categories": ["SEO", "WordPress plugins"], "confidence": 100, "version": "27.3", "source": "http" },
{ "name": "Nginx", "categories": ["Web servers", "Reverse proxies"], "confidence": 100, "version": "", "source": "http" },
{ "name": "PHP", "categories": ["Programming languages"], "confidence": 100, "version": "", "source": "http" },
{ "name": "MySQL", "categories": ["Databases"], "confidence": 100, "version": "", "source": "http" },
{ "name": "Google Tag Manager", "categories": ["Tag managers"], "confidence": 100, "version": "", "source": "http" },
{ "name": "Let's Encrypt", "categories": ["SSL/TLS certificate authority"], "confidence": 70, "version": "", "source": "tls" }
],
"categories": {
"Ecommerce": ["WooCommerce"],
"WordPress plugins": ["WooCommerce", "Yoast SEO"],
"CMS": ["WordPress"],
"PaaS": ["WordPress VIP"],
"SEO": ["Yoast SEO"]
},
"meta": { "status_code": 200, "tech_count": 14, "scan_depth": "full" },
"cached": false,
"response_ms": 1646
}
That single call is a brief: WooCommerce 11.1.0 on WordPress 7.0.2, running on WordPress VIP managed hosting behind Nginx, with Yoast SEO and Google Tag Manager. The categories map makes the store check a one-line lookup in code — test whether categories["Ecommerce"] contains WooCommerce — and the hosting entry tells you the account size bracket before you have spoken to anyone.
Boolean Yes/No With GET /check?tech=WooCommerce
When all you want is a flag, /check answers directly without making you filter an array:
curl -s "https://detectzestack.p.rapidapi.com/check?url=yithemes.com&tech=WooCommerce" \
-H "X-RapidAPI-Key: YOUR_KEY" \
-H "X-RapidAPI-Host: detectzestack.p.rapidapi.com" | jq '.'
{
"domain": "yithemes.com",
"technology": "WooCommerce",
"detected": true,
"confidence": 100,
"version": "9.3.6",
"categories": ["Ecommerce", "WordPress plugins"],
"response_ms": 388,
"cached": false
}
The tech parameter is case insensitive and the response echoes back the canonical name. Branch on detected.
Reading the Response: Confidence, Version, and Categories
Three fields carry most of the meaning, and each has a failure mode worth naming:
confidence—100for WooCommerce, because the plugin asset path is an exact match rather than a heuristic. Lower confidence values appear elsewhere in the same response for inferred signals: note theLet's Encryptentry above at70, derived from the TLS certificate chain.version— populated from the?ver=query string on the enqueued script. Asset optimization and CDN rewriting sometimes strip that query string, in which caseversioncomes back empty while the detection itself still succeeds. Empty means "not readable", not "not installed", and you should never treat it as a data error.categories— WooCommerce always carries bothEcommerceandWordPress plugins. If you are slicing a large scan by category, decide which one you are filtering on, because the same detection appears under both.
The source field takes values like http, dns, and tls depending on which layer produced the evidence. WooCommerce is only ever http: no DNS record or certificate property reveals a WordPress plugin, so a successful HTML fetch is a hard prerequisite for detecting it at all.
Scanning a List of Domains With POST /analyze/batch
Batch is how you work through a list. It accepts up to 10 URLs per request, scans them concurrently, and gives each item its own error field so one bad domain does not fail the whole batch:
curl -s -X POST "https://detectzestack.p.rapidapi.com/analyze/batch" \
-H "X-RapidAPI-Key: YOUR_KEY" \
-H "X-RapidAPI-Host: detectzestack.p.rapidapi.com" \
-H "Content-Type: application/json" \
-d '{"urls": ["woocommerce.com", "yithemes.com", "kinsta.com", "wpbeginner.com"]}' \
| jq -r '.results[]
| select(.result.technologies[]?.name == "WooCommerce")
| .result.domain'
Run against those four real domains, that filter returns woocommerce.com and yithemes.com. Both kinsta.com and wpbeginner.com are WordPress sites and neither is a store — a clean demonstration that the WordPress filter and the WooCommerce filter select genuinely different populations.
Each URL in a batch counts as one request against your monthly quota, so a 10-URL batch costs 10 requests. Batching saves round trips and wall-clock time, not quota. For lists in the hundreds or thousands, the batch scanning guide covers pacing, retries, and cost.
Here is the same idea as a Python pipeline that keeps failed fetches out of your negatives:
import csv
import requests
API = "https://detectzestack.p.rapidapi.com/analyze/batch"
HEADERS = {
"X-RapidAPI-Key": "YOUR_KEY",
"X-RapidAPI-Host": "detectzestack.p.rapidapi.com",
"Content-Type": "application/json",
}
BATCH_SIZE = 10 # API maximum per request
def scan(domains):
"""Scan up to 10 domains, yielding (domain, technologies, status_code)."""
resp = requests.post(API, headers=HEADERS, json={"urls": domains}, timeout=60)
resp.raise_for_status()
for item in resp.json()["results"]:
result = item.get("result")
if not result:
print(f" skipped {item['url']}: {item.get('error', 'no result')}")
continue
status = result.get("meta", {}).get("status_code")
yield result["domain"], result.get("technologies", []), status
def main():
with open("domains.txt") as f:
domains = [line.strip() for line in f if line.strip()]
stores, inconclusive = [], []
for i in range(0, len(domains), BATCH_SIZE):
chunk = domains[i:i + BATCH_SIZE]
print(f"Scanning {i + 1}-{i + len(chunk)} of {len(domains)}...")
for domain, techs, status in scan(chunk):
if status != 200:
# No page HTML means no WooCommerce assets to match on.
inconclusive.append(domain)
print(f" {domain}: status {status}, inconclusive")
continue
by_name = {t["name"]: t for t in techs}
woo = by_name.get("WooCommerce")
if not woo:
continue
wp = by_name.get("WordPress")
stores.append({
"domain": domain,
"woo_version": woo.get("version", ""),
"wp_version": wp.get("version", "") if wp else "",
"stack": ", ".join(sorted(by_name)),
})
print(f" {domain}: WooCommerce {woo.get('version') or 'version unknown'}")
with open("woocommerce_stores.csv", "w", newline="") as f:
writer = csv.DictWriter(
f, fieldnames=["domain", "woo_version", "wp_version", "stack"]
)
writer.writeheader()
writer.writerows(stores)
print(f"\n{len(stores)} WooCommerce stores out of {len(domains)} domains.")
print(f"{len(inconclusive)} inconclusive, retry later: {inconclusive}")
if __name__ == "__main__":
main()
The guard that matters most: check meta.status_code. WooCommerce is detected only from rendered HTML. If a site answers your scan with a 403 or 429 instead of its page, there is no asset path to match and the detection will be missing on a store that is entirely WooCommerce-built. A live scan of barefootbuttons.com while writing this returned "status_code": 403 and zero technologies — which says nothing at all about what that site runs. Treat any non-200 as inconclusive, not negative.
WooCommerce vs Shopify: Telling Ecommerce Platforms Apart
Both are ecommerce platforms and both land in the Ecommerce category, but they are detectable in structurally different ways, and the difference decides how much you can trust a negative result.
| WooCommerce | Shopify | |
|---|---|---|
| Detection layer | http only |
http and dns |
| Primary signal | /wp-content/plugins/woocommerce/ asset path |
CNAME to Shopify infrastructure; cdn.shopify.com assets |
| Version available | Yes, from the script ?ver= string |
No plugin version to report |
| Always paired with | WordPress, PHP, MySQL | Nothing — the platform is the whole stack |
| Survives a blocked fetch | No | Yes, via DNS |
That last row is not theoretical. A live scan of deathwishcoffee.com came back with "status_code": 429 — the site rate-limited the request and returned no page — and yet the response still contained:
{
"name": "Shopify",
"categories": ["Ecommerce"],
"confidence": 80,
"source": "dns"
}
The DNS record identified the platform even though the HTML never arrived. Confidence is 80 rather than 100 precisely because the corroborating HTTP evidence was missing. A WooCommerce store behind the same rate limiter would have returned nothing.
The practical takeaway for anyone building an ecommerce prospect list: a missing Shopify detection on a 200-status scan is fairly strong evidence of absence, while a missing WooCommerce detection on any non-200 scan is no evidence at all. For a fuller side-by-side, see Shopify vs WooCommerce detection and how to detect Shopify.
Practical Use Cases for WooCommerce Detection
Prospecting WordPress Ecommerce Agencies and Plugin Vendors
If you sell WooCommerce extensions, payment integrations, hosting, or store maintenance, the detection is your addressable market filter — but the version field is what makes it a priority queue. A store several minor versions behind current is a care-plan lead; one on the latest release is a growth lead. Layer the surrounding signals from the same /analyze response:
- Hosting tells you budget. The real scans in this article surfaced
WordPress VIPon woocommerce.com andKinstaon yithemes.com — both enterprise-tier managed hosts. See detecting WP Engine for the same play on another host. - Plugin neighbours tell you the store's maturity. An SEO plugin, a consent manager, and a tag manager together describe a site someone actively runs; detecting Yoast SEO covers one of those signals in depth.
- Page builder tells you who does the work. A WooCommerce store also running Elementor is likely maintained by a marketer or an agency rather than a developer.
Competitive and Market Share Research
Because the version number is exposed, WooCommerce is one of the few technologies where you can measure adoption of a specific release across a sample rather than just presence. Scan a fixed list on a schedule, record the version each time, and you have a version-distribution curve and an upgrade-lag figure for your segment. The same repeated scans catch platform migrations, the highest-intent moment in the ecommerce sales cycle — tracking technology changes over time covers the mechanics, and ecommerce tech stack analysis covers what else to record alongside the platform.
Limitations and False Negatives to Expect
An absent WooCommerce detection is an unknown, not a no. Four causes, in rough order of how often they bite:
- The scan never saw the page. Bot protection, rate limiting, and geo-blocking all produce a non-200 response and therefore zero HTML detections. This is the dominant cause on ecommerce domains specifically, because stores run aggressive bot defences. Check
meta.status_codeevery time. - The scanned page does not load store assets. Scans hit the homepage by default. A business whose homepage is a marketing page with the shop on
/shopor a subdomain may not enqueue WooCommerce assets on the URL you scanned. If a domain matters, scan the shop path directly. - Asset rewriting. Optimization plugins and CDN pipelines that concatenate, rename, or proxy scripts can destroy the recognisable path. This usually costs you the version first and the detection second.
- Headless WooCommerce. A store using WooCommerce purely as a backend behind a custom frontend serves no WordPress assets on the public site at all. Rare, but growing, and undetectable from the frontend by design.
The discipline that follows is a three-state model in your pipeline — detected, not detected, inconclusive — rather than a boolean column. The Python example above keeps the inconclusive rows in their own list for exactly this reason.
Get Your API Key and Start Detecting
Validate the whole approach before paying for anything. The keyless /demo endpoint runs a full scan on any domain:
curl -s "https://detectzestack.com/demo?url=yithemes.com" \
| jq '{domain, ecommerce: (.categories["Ecommerce"] // []), status: .meta.status_code}'
{
"domain": "yithemes.com",
"ecommerce": [
"WooCommerce"
],
"status": 200
}
When you are ready for the authenticated endpoints, plans are the standard DetectZeStack tiers, and every URL scanned counts as one request:
| Plan | Price | Requests / month |
|---|---|---|
| Basic | Free | 100 |
| Pro | $9 | 1,000 |
| Ultra | $29 | 10,000 |
| Mega | $79 | 50,000 |
Results are cached for 24 hours by default, and a cache hit returns "cached": true with a near-zero response_ms. The free plan's 100 monthly requests are enough to run a real slice of your target list end to end before spending anything.
Conclusion
WooCommerce is invisible at the DNS layer and obvious in the HTML. The whole detection rests on one artifact — the /wp-content/plugins/woocommerce/ asset path, with the version riding along in the ?ver= query string — and once you know that, both the capability and the limit follow directly. You get a plugin version on almost every hit, which is unusual and genuinely useful for segmentation. You get nothing at all when the fetch fails, which is common on ecommerce domains and will silently poison a list that treats absence as a negative.
Start with /demo on a store you already know, use /check when you want a boolean, /analyze when you want the surrounding stack, and /analyze/batch when you have a list. Keep the status_code guard in every pipeline, and what you build will be a list you can actually act on.
Related Reading
- Shopify vs WooCommerce Detection — Side-by-side signals for the two dominant ecommerce platforms
- Check If a Website Uses WordPress — The platform layer WooCommerce always sits on
- Ecommerce Tech Stack Analysis — What else to record alongside the store platform
- Detect What CMS a Website Uses — When you do not know the platform yet
- Find Companies Using WordPress — Building the wider list WooCommerce filters down
- How to Batch Scan 1,000 Websites — Scaling the batch endpoint past a handful of domains
- Find Companies Using Elementor — A page-builder signal to layer on for qualification
Start Detecting WooCommerce Stores Today
100 free API requests/month. No credit card required. Detect WooCommerce and thousands of other technologies.
Get Your Free API Key