# SMM panel software-and-infrastructure survey — methodology

**Survey window:** every page in this dataset was fetched between
**2026-09-09T20:26:05Z and 2026-09-09T20:38:54Z**.
**Surveyor:** Novamya editorial.
**Sample:** 84 candidate domains; 76 answered `200` on a homepage or a catalogue and are the
corpus every percentage below is measured over.

## 1. The question

The SMM-panel market presents itself as hundreds of separate businesses. This survey asks a
narrower, checkable version of that: **of the panels whose public pages we could read, how many
are running the same software, and how many sit on the same infrastructure?**

It is a survey of *what the pages say about themselves* — asset hosts, template class names,
hard-coded vendor keys, DNS, ASN, TLS. It is not an ownership investigation and it cannot be one.
Section 6 says exactly which conclusion each piece of evidence does and does not license.

## 2. Collection rules

These are the site's published standards, and `harvest.py` enforces all of them in code:

1. **Public pages only.** No account was created, no order placed, no form submitted, no login
   attempted, nothing behind auth was requested. Four URLs per host: `/robots.txt`, `/`, the public
   catalogue, and `/favicon.ico`.
2. **robots.txt first, and obeyed.** `robots.txt` is fetched before anything else and parsed with
   Python's `urllib.robotparser` against our own user-agent token, `Novamya-Research`. A disallowed
   path is skipped and the skip is written to `fetchlog.tsv` as a row with status `SKIPPED_ROBOTS`.
   **In this run no host disallowed any path we asked for, so that count is zero.** Four hosts
   (`morethanpanel.com`, `smmkings.com`, `smmworld.org`, `thesocialmediagrowth.com`) do carry a
   `Disallow: /`, but in every case it is scoped to named commercial crawlers — `AhrefsBot`,
   `SemrushBot`, `MJ12bot`, `Amazonbot`, `Bytespider` and similar — not to `User-agent: *`, and we
   are none of them. The retained `pages/*.robots.txt.gz` files let anyone check that reading.
3. **One request per host per two seconds**, enforced by a per-host timestamp in the fetcher. You
   can verify it from `fetchlog.tsv`: sort the rows of any one domain by timestamp and no two rows
   that carry an HTTP status code are less than two seconds apart. Rows closer than that are the
   synthetic `NOT_FOUND` bookkeeping rows, which record a decision, not a request.
4. **A truthful user agent.** Every request went out as
   `Novamya-Research/1.0 (+https://novamya.com/about/)`. We did not present as a browser. This is a
   deliberate difference from Novamya's earlier price survey, which used a desktop Chrome string,
   and it costs us data: several hosts that answered that survey answered this one with `403`.
   That cost is recorded in section 4 rather than engineered around.
5. **Everything is retained.** For every fetch: the body gzipped under `pages/`, the full response
   headers gzipped beside it, and a row in `fetchlog.tsv` with the URL requested, the URL that
   answered after redirects, the HTTP status, the byte count, the `Server` and `X-Powered-By`
   headers, a SHA-256 of the body and a UTC timestamp. The one exception is `/favicon.ico`: it is a
   binary, so it is hashed into the log and the bytes are dropped, to keep this directory small.
6. **Fetches ran from a Helsinki host.** This workstation is in Iran and a large share of these
   panels geo-block it outright, which would have silently biased the sample. Network observations
   (DNS, rDNS, ASN, TLS) were made from the same host in the same window and are in `infra.jsonl`.

## 3. How the 84 candidates were chosen

1. **The 15 panels of Novamya's own price survey** (2026-09-02), plus the 10 that survey had to
   exclude. Carrying the exclusions forward matters: they are the panels that hide their catalogue,
   and whether they run the same software is exactly the question here.
2. **Twenty-five Serper API calls covering 22 distinct queries** (`gl=us`, `hl=en`), routed through
   an OVH Montréal relay because this workstation's IP is blocked by that API. Three first attempts
   were rejected as malformed and re-issued. The queries were of two kinds: listicle queries ("best smm panel 2026
   list…") and footprint queries designed so that the *result domains themselves* are panels
   (`"Rate per 1000" "Min order" "Max order" smm panel services`). The query strings are the
   record; the raw API responses are third-party output and are not republished.
3. **Public directory and listicle pages** — `socpanel.com`, `smmdir.com`, `rankpanels.com`,
   `smmpanellist.com`, `cheapestsmmpanels.com`, `best-smm.com`, `smmpanelserver.com`,
   `smmtrustpanel.com` and 50-odd "top 10 panels" articles — scraped for outbound panel domains.
   This was the weakest source by yield: the two largest directories mask the domains they rank
   (`smmdir.com` prints `sm******.com` and sells the real names by subscription), and
   `rankpanels.com` loads its table from a JSON API after page load. Both are noted here because a
   reader may reasonably expect a directory to be the obvious source, and it is not.

**Two of the 84 — `smmlaunch.com` and `adderpanel.com` — are operated by the company that publishes
Novamya**, and are labelled `publisher-owned (disclosed)` in `fingerprints.csv`. They are in the
sample for the same reason they were in the price survey: leaving them out would hide the one
relationship in this data we can verify from the inside. They are not exceptions to any finding.
Both run the same vendor software as the majority, both sit in the same `/24` as 42 other panels,
and both carry the same hard-coded vendor captcha key as 47 others.

## 4. What was excluded, and why

| Excluded | Reason |
|---|---|
| `growfollows.com`, `indiansmmbulk.com`, `indianvipsmm.com`, `smmbin.com`, `smmquality.com`, `thesocialmediagrowth.com` | HTTP `403` to our truthful research user-agent. All six sit behind Cloudflare. We did not retry with a browser string. |
| `smmcard.org`, `smmresellpanel.com` | Connection failed entirely (curl status `000`) inside the 45-second timeout. |

Those eight are **kept as rows in `fingerprints.csv`** with `software_family = unreadable`, and are
in the denominator of "84 candidates" and out of the denominator of "76 we could read". A panel
that refused us is data.

Two further categories are *retained but limited*:

- **33 of the 76 readable panels published no machine-readable catalogue** to us. Some redirect
  `/services` to the homepage (`peakerr.com`, `justanotherpanel.com`, `smmfollows.com`,
  `bulkfollows.com`, `n1panel.com`, `yoyomedia.in` among them — `fetchlog.tsv` carries both the URL
  requested and the URL that answered); others load the table over JavaScript we did not execute.
  Those panels count towards every software and infrastructure figure and towards none of the
  catalogue figures.
- **`socpanel.com`, `smmdir.com`, `rankpanels.com` and the other directories are not in the sample
  at all.** They list other panels' services; counting them would double-count.

## 5. How the fingerprints were taken

Everything in `fingerprints.csv` is derived by `analyse.py` from the stored bytes. No field is
inferred from how a page looks.

**Software family.** An ordered list of literal-string rules (`RULES` in `analyse.py`), each
matched against the homepage, the catalogue page and the response headers concatenated. The rule
that fired is written into `software_evidence`, so every classification carries its own receipt.
Frameworks are separated from products: Laravel, Next.js and WordPress are recorded when seen but
are **never counted as a software family**, because two Laravel panels are no more "the same panel"
than two websites written in PHP.

Four independent tells identify the dominant family, and they agree:

- `storage.perfectcdn.com` as the asset host — 52 panels;
- shared `global/<hash>.js` bundles served from it, the vendor's own build artefacts. One file,
  `odzrsnmblt1it35c.js`, is served by 50 panels; three more by 34 each;
- a per-customer tenant folder in the same asset host (`storage.perfectcdn.com/<tenant>/style.css`);
- **the vendor's captcha endpoint with the API key baked into the template**:
  `altcha.perfectpanel.com/v1/challenge?apiKey=key_1jhlt1fhm00b2s721es`. **49 panels serve that
  identical key.** It is a public widget key present in the HTML of every one of those pages; it is
  quoted here because it is the single hardest software fingerprint in the dataset. It identifies a
  vendor build, and nothing else.

**Tenant ids.** The tenant folder is only counted when it appears on the panel's *own* chrome — its
stylesheet, script bundle, favicon or manifest. A tenant id appearing on a `.png` inside a service
description is a picture someone pasted in, and seven panels in this corpus embed an image out of a
*different* tenant's folder that way. Those are recorded separately in
`other_tenant_ids_embedded_in_content` and are excluded from the tenant count. An earlier version of
this analysis did not make that distinction and reported four pairs of panels "sharing a tenant
account"; three of the four were a screenshot in a service description, and the fourth was a
favicon on one side and a content image on the other. **After the correction, no two panels in this
corpus share a tenant id.**

**Catalogue.** Service names are read verbatim. Two extractors run and the one that returns more
rows wins, with the winner recorded in `catalogue_extractor`: a `<table>` whose header row names a
per-1,000 rate column (the same test the price survey used, kept identical so the two datasets
join), and a card template whose service name sits in an element classed `service-name`, `ss-name`,
`services-title` or `s-title`. For similarity, names are lowercased, stripped of emoji, and every
run of digits is replaced with `#`, so a service id or a "50K/Day" speed does not defeat the match.

**Infrastructure.** `dig` for A records and NS, `dig -x` for reverse DNS, Team Cymru's whois
interface for the announcing ASN, and `openssl s_client` for the certificate's issuer, validity
dates and SAN list. All of it is in `infra.jsonl` with its own timestamp.

**Currency** is the symbol that actually sits in front of the numbers on the catalogue page, not
what the currency switcher offers. Those are very different measurements: 55 panels *price* in USD
and 6 in INR, while 39 panels *mention* INR because they have a switcher. Both columns are in the
CSV, named for what they are.

**Support channels** count only an actual contact handle — a `t.me/` link, a `wa.me/` number, a
`mailto:`, a live-chat widget. The bare words "telegram", "whatsapp" and "discord" are platform
names every one of these catalogues sells services for; counting those scored 73 of 84 panels as
offering Telegram support, which is a measurement of the product list, not of the support desk.

## 6. What the evidence supports, and what it does not

**Shared software is shared software, and that is all.** 54 of the 76 readable panels run one
vendor's product. That is a fact about the supply of panel software. It is *not* evidence that
those 54 storefronts share an owner, and this dataset contains a specific reason to believe they do
not: each of the 50 panels whose tenant folder we could read has **its own distinct tenant id**. 50
panels, 50 separate vendor accounts, no overlap. Same product, different customers — exactly what
buying a SaaS licence looks like.

**Shared hosting is shared hosting.** 44 of the 84 candidates resolve inside `152.228.155.0/24`, a
single OVH /24, and 48 panels share an IP address with at least one other panel — six of them on
`152.228.155.65` alone. On a multi-tenant platform this is the *expected* result of buying the same
hosted product, not a discovery about ownership. Fifty of the 84 also use ClouDNS nameservers, 45 of them the identical pair
`dns1.cloudns.net` / `dns2.cloudns.net`, which is that platform's default. Read together with the distinct tenant
ids, the honest reading is one hosting platform with many customers.

**Shared TLS certificates would have been strong evidence. We found none.** Every certificate in
this corpus covers one domain and its `www`. There is no certificate in this dataset naming two
surveyed panels, so the strongest single-piece evidence this method can collect is simply absent —
and it is reported as absent rather than dropped.

**Shared catalogues turned out to be weaker than the hypothesis expected.** Across the 903 pairs of
panels with a readable catalogue, the *highest* name-set overlap observed is
**0.468 between `smmcost.com` and `smmbind.com`, sharing 3,064 verbatim service names**. Exactly
one pair exceeds 0.30, and nine exceed 0.10. At the 0.60 threshold set in advance there are zero
clusters. Two panels sharing thousands of identical service-name strings is consistent with a
shared upstream supplier, and equally consistent with one panel importing the other's price list —
this survey cannot separate those, and it does not try.

**The "Powered by RPA™" string is not what it looks like.** It was the tell that prompted this
study, on the theory that it identified a common codebase. Read in context it is a *service name* —
"RPA™ - Real Popup Ads" — sitting in a catalogue row with a price, a minimum and a maximum. Six
panels carry it, and what it evidences is a shared upstream supplier of pop-under traffic, not
shared software. It is written down here because the study's own starting premise was wrong about
it.

**Nothing here is about delivery, quality or honesty.** Not one order was placed. This dataset
cannot tell you whether any panel delivers, and it makes no claim that any named business is
misleading anyone. The finding is about market structure: a buyer comparing storefronts is, much of
the time, comparing tenants of one product.

**Sample bias to keep in mind.** This is a sample of panels that are *findable* — indexed by
Google, linked from directories, or already known to us. Panels that block a self-identified
research agent are absent by construction, and six of them are named in section 4. A market census
this is not; 84 domains is a sample of a market that plausibly runs to thousands.

## 7. Reproducing this

```
python3 harvest.py     # network: panels.tsv -> pages/, fetchlog.tsv, infra.jsonl
python3 analyse.py     # offline: pages/ + fetchlog.tsv + infra.jsonl -> fingerprints.csv,
                       #          clusters.json, stats.json
```

`analyse.py` reads and writes the directory it lives in and never touches the network, so running
it against the retained `pages/` reproduces every number quoted above and every number in
`stats.json`. `harvest.py` will not reproduce them — it re-fetches, and these sites change. If a
figure in the article does not come out of `analyse.py`, the article is wrong and we want to be
told.
