Skip to main content

Measure Traffic Quality

Standard analytics measures volume: how many sessions, how many pageviews, how many “new” users. It cannot tell you how much of that traffic is masked, spoofed, or coordinated. ShieldLabs measures quality: every visit carries an explainable Risk Score (0-100) and the signals behind it, so you can split a noisy number like “10,000 visits” into traffic you can trust and traffic you cannot. This recipe shows how to read the dashboard to grade traffic, how to rank each acquisition source by the anonymous-traffic share it delivers, and how to export the raw Data into your own BI to compute cost per real visitor. ShieldLabs scores the request; your reporting and budget decisions stay yours.
The Risk Score is 0-100, hard-capped at 100, in four bands: Clean (0-9), Low (10-29), Medium (30-59), High (60-100). A higher score means more anonymous or masked traffic, not a confirmed verdict: a legitimate visitor can score high behind a corporate proxy, a VPN, or a privacy browser. For traffic-quality reporting you are looking at the shape of the distribution across many requests, where individual false positives wash out.

Volume vs quality, in one number

A dashboard that only reports volume treats every session as equal. ShieldLabs attaches a per-request risk dimension, so the same 10,000 visits become a quality breakdown you can act on.
Standard analyticsShieldLabs
Unit countedSessions, pageviewsIdentified requests + Risk Score
”10,000 visits” means10,000 equal sessionsA distribution across Clean / Low / Medium / High
Can it see VPN, proxy, Tor, anti-detect routingNoYes, as named signals in Details
Returning visitor after cleared cookiesCounted as newRecognized by DeviceID (same browser)
Per-source verdictVolume and conversionsVolume, conversions, and risk share
For quick reporting it helps to collapse the four bands into three plain buckets. This is a reporting convention, not a product feature: the bands are still Clean / Low / Medium / High everywhere in the product.
BucketBandsWhat is usually in it
CleanClean (0-9), Low (10-29)Direct connections, ordinary browsers, the bulk of healthy traffic
SuspiciousMedium (30-59)VPN, proxy, privacy relay, datacenter IP, timezone mismatch (one or two overlapping signals)
DangerousHigh (60-100)Tor, anti-detect browsers, OS mismatch, port-scan proxying (strong anonymity or abuse signals)
A campaign sending 95% Clean traffic and one sending 40% Dangerous traffic can report identical visit counts in Google Analytics or Vercel Analytics. The Risk Score is what tells them apart.

Read the dashboard

Two cards on the Overview tab give you the whole-traffic picture, and Traffic Sources breaks it down by where the traffic came from. Filter both by project and date range using the controls at the top of the page.
1

Read the Traffic Score card

On Overview, the Traffic Score card shows a gauge with the band label, a Traffic Risk metric (the average request risk for the period, where 0 is ideal and 100 is very bad), and Requests Checked (how many requests were analyzed). Below the gauge, a stacked bar and legend show the count and percent of requests in each band: Clean, Low Risk, Medium Risk, High Risk. This is your quality split for all traffic at a glance.
2

Rank sources by risk

Open Traffic Sources. The Channels table lists Google Ads, Meta, TikTok, LinkedIn, X, Organic Search, Referral, Direct, and Other. Each row shows requests, traffic share, and a Risk Badge rendered as <score> <level> (for example 71 High or 8 Clean). Sort by Traffic Risk to find the channel sending the most masked traffic.
3

Drill into referrers and UTM

The Source details table toggles between Referrers and UTM Parameters. Under UTM you can inspect Source, Medium, Campaign, Term, or Content, each with its own request share and Risk Badge. This is how you isolate the single affiliate, creative, or campaign delivering the anonymous traffic inside an otherwise healthy channel.
4

Export the raw records for BI

Go to the Data tab and use Export to pull the per-request records as JSON or CSV. Exports are free (they do not bill requests). Load them into your warehouse or BI tool to join risk against spend, conversions, and revenue. The next section covers the columns you get.
The Traffic Score card counts in requests, while the Patterns Summary card on the same Overview tab counts in unique visitors and sessions. They use different denominators on purpose, so do not expect their totals to reconcile. For “how risky is my traffic,” read Traffic Score; for “which identities cross abuse thresholds,” read Abuse Patterns.
Google Analytics and Vercel Analytics count by a first-party cookie or client id. Clear cookies, open an incognito window, switch devices, or rotate your IP, and they count a brand-new user every time. That inflates “new visitors” and quietly loses your returning ones. ShieldLabs counts by a fingerprint-derived identity. The DeviceID is derived from dozens of stable browser components (canvas, WebGL, audio, fonts, screen, navigator, timezone, and more), not stored in a cookie, so a returning person on the same browser keeps the same identity even after clearing cookies or using incognito. They are not miscounted as new.
GA / Vercel count byShieldLabs counts byResult
Identity basisFirst-party cookie / client idDeviceID (derived from the browser environment)Survives cookie clear and incognito
Cleared cookiesNew user each timeSame DeviceIDReturning visitors stay returning
Rotated IPOften a new userSame DeviceIDOne person, one identity
Different browser, same machineNew userNew DeviceIDCounted separately (honest boundary)
Two honest caveats to keep in your reporting: the dashboard tooltip itself calls these estimated unique visitors, so do not promise exact counts, and DeviceID is browser-bound, so the same person on a second browser is counted separately. There is no cross-browser recognition today. See Identifiers for the full mechanics and the Visitors view for how “new” is determined.

Capture the source on every visit

The snippet already collects channel, referrer, and UTM attribution for every request, so the Traffic Sources tables populate without extra work. You only need the standard install on the page that receives the traffic.
<!-- Landing page that paid and organic traffic arrives on -->
<script type="module">
  const mod = await import('https://cdn.shieldlabs.ai/snippet.js?publicKey=YOUR_PUBLIC_KEY');
  mod.checkAnonymous((serverResponse, requestID) => {
    // Optional: stash the requestID so you can attribute a later
    // conversion back to this scored visit.
    document.cookie = `shield_rid=${requestID}; max-age=3600; SameSite=Lax`;
  });
</script>
Make sure your UTM parameters are on the inbound links. ShieldLabs records utm_source, utm_medium, utm_campaign, utm_content, utm_term, the referrer_domain, and the resolved channel for each request. See Snippet for the framework variants and CSP requirements.

Compute cost per real visitor

The point of grading traffic by source is to stop paying click prices for masked traffic. “Cost per real visitor” reweights spend against the share of a source’s traffic you can actually trust. Export the Data records, group by source, and divide spend by the Clean-bucket count instead of the raw count.
// One row per source for a reporting window. `spend` comes from your
// ad platform; `requests` and `bands` come from the ShieldLabs export.
const sources = [
  { source: "google_ads", spend: 4000, requests: 10000, bands: { clean: 8800, low: 700, medium: 350, high: 150 } },
  { source: "affiliate_x", spend: 3000, requests: 9000,  bands: { clean: 2200, low: 600, medium: 2400, high: 3800 } },
];

function trafficQuality({ source, spend, requests, bands }) {
  // Reporting convention: Clean bucket = Clean + Low bands.
  const realVisitors = bands.clean + bands.low;
  const dangerous = bands.high;

  return {
    source,
    requests,
    realVisitors,
    cleanShare: realVisitors / requests,
    dangerousShare: dangerous / requests,
    costPerClick: spend / requests,
    costPerRealVisitor: spend / Math.max(realVisitors, 1),
  };
}

for (const s of sources) {
  const q = trafficQuality(s);
  console.log(
    `${q.source}: $${q.costPerClick.toFixed(2)}/click -> ` +
    `$${q.costPerRealVisitor.toFixed(2)}/real visitor ` +
    `(${(q.cleanShare * 100).toFixed(0)}% clean, ` +
    `${(q.dangerousShare * 100).toFixed(0)}% dangerous)`
  );
}
// google_ads: $0.40/click -> $0.42/real visitor (95% clean, 2% dangerous)
// affiliate_x: $0.33/click -> $1.07/real visitor (31% clean, 42% dangerous)
On a cost-per-click basis affiliate_x looks cheaper. On a cost-per-real-visitor basis it is more than twice as expensive, because most of its traffic is masked. That is the reallocation decision standard analytics cannot surface. The same pattern applies to paying out conversions, covered in Affiliate Fraud.

Pull the data programmatically

You have two programmatic paths, and they carry different fields. The Data export (JSON or CSV from the Data tab) is the one that includes the per-request source attribution: channel, referrer_domain, and the utm_* fields, alongside the Score and signal columns. That is the export to join against ad spend for cost-per-real-visitor reporting. Exports are free. The History API is for reading scored records by identifier in real time. Query by identifier and the response returns newest-first snapshots with the Score, the Details behind it, and the network-intelligence fields, but not the channel/UTM attribution. Use it to reconcile or enrich, then join back to the export on RequestID when you need source attribution. The History API bills one request per returned row (an empty result still bills one), while dashboard views and exports are free.
# Snapshots for one visitor, newest first. Each row carries Score,
# the Details behind the score, and the network-intelligence fields.
curl "https://api.shieldlabs.ai/myshop.com:YOUR_SECRET_KEY/history/visitor_id/c4a2e9b1-5f8d-4c3a-8e7b-2a1f0d9c8b76?limit=50"
Each snapshot is a superset of the webhook body and adds the network-intelligence fields you can keep server-side:
{
  "RequestID": "8f1d0c2a-7b3e-4a9c-9d2f-1e6a5b4c3d21",
  "VisitorID": "c4a2e9b1-5f8d-4c3a-8e7b-2a1f0d9c8b76",
  "DeviceID": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "IP": "203.0.113.42",
  "Country": "US",
  "Score": 90,
  "Details": [
    { "Value": 60, "Description": "OS Mismatch" },
    { "Value": 30, "Description": "Browser VPN/Proxy" }
  ],
  "ConnectionType": "Residential",
  "WebRtcCountry": "US",
  "Browser": "Chrome",
  "DeviceType": "Desktop",
  "LastRequestTime": "2026-06-16T18:00:21.685Z"
}
For source-level reporting, lead with the Data export (it carries channel and utm_*) and use the History API to pull fresh per-identifier records as needed. If you would rather build the report in real time as traffic arrives, consume the webhook instead and aggregate the score per source in your own store, capturing the attribution from the inbound request yourself. Webhooks are at-most-once with no retries, so make the handler idempotent on RequestID, and for guaranteed completeness reconcile against the History API.
Two scores can arrive for one visit. The Phase: "initial" webhook fires about a second after the request, and an optional Phase: "update" fires after the WebRTC real-IP check refines the result. When you aggregate for reporting, key on RequestID and use the latest phase so a single visit is not double-counted.

What to do with the answer

  • A channel or campaign with a high Dangerous share is the first place to cut or renegotiate spend, especially affiliate and referral sources.
  • A source that looks expensive per click but is mostly Clean may be your best traffic once reweighted to cost per real visitor.
  • Rising Medium and High share over time on Direct or Organic Search is a signal to look at the Abuse Patterns tab for coordinated activity behind the volume.
  • Pair this report with the Visitors view to separate genuinely returning people from cookie-churned “new” sessions, so your retention numbers reflect reality.

Where to go next

If you have not installed the snippet and a webhook yet, start with the Quickstart. To understand the score and the named signals that drive the quality split, read Risk Score and Signals. For per-source payout decisions, see Affiliate Fraud, and for billing on the per-request model, see Billing.