DEV Community

Timothy Kelvin
Timothy Kelvin

Posted on

Fingerprinting a website's tech stack from a single HTTP request (no headless browser)

I wanted a fast way to answer "what's this site built with?" — CMS, ecommerce platform, JS framework, analytics tags, CDN, payment/chat widgets — without spinning up a full headless browser per URL.

The approach

One plain HTTP fetch of the homepage via Crawlee's CheerioCrawler, then check three things against a signature table:

  • Response headers (Server, X-Powered-By, CF-Ray, X-Vercel-Id, etc.)
  • The <meta name="generator"> tag
  • <script src> domains on the page

Same general idea as Wappalyzer, just a much smaller, hand-written signature set — about 30 technologies across 7 categories (CMS, ecommerce, JS frameworks, analytics, CDN/hosting, payment, live chat). Each signature is a test(context) function:

{
  name: 'WordPress',
  category: 'cms',
  test: ({ generator }) => generator.includes('wordpress'),
}
Enter fullscreen mode Exit fullscreen mode

context gives you lowercased headers, html, scriptSrcs, and generator — cheap to check, no DOM traversal needed for most signatures.

What it deliberately doesn't do

  • No headless browser. Everything it reads is already served to any visitor's plain HTTP request — no JS execution, no login, no bypassing any protection.
  • No guessing. If nothing in the signature table matches, it returns an empty result for that category rather than a fuzzy best-guess. A smaller-but-honest signature set beats a big one that's confidently wrong.

Where it fell short (and stayed that way, on purpose)

Detection accuracy is bounded by however many signatures I've hand-written — it's ~30 technologies, not the hundreds Wappalyzer tracks. Sites using something niche just come back with an empty category instead of a wrong guess. That's a real limitation, not a bug, and the tradeoff I'd make again: adding a signature is one function, so it grows exactly as fast as real usage demands it, not ahead of it.

Try it

If you've got a signature I'm missing, signatures.js is a small file — happy to take PRs or just hear about the gap.

Top comments (0)