What is paywall bypass?
Paywall bypass is when a reader gets past a metered or subscription wall without paying — most often by resetting the free-view count. They clear cookies, open a private window, or rotate their IP so the meter forgets them and starts over. Each reset mints a freshCookieID and therefore a fresh VisitorID, which is exactly why a cookie-keyed meter forgets the reader.
How ShieldLabs surfaces it
ShieldLabs derives a durable, server-derived DeviceID for each view that holds through those resets, so you can count metered views per device on an identifier the reader cannot wipe. Each view also carries aRequestID, the join key your snippet hands to your backend to look up that view’s DeviceID before it serves or walls the article.
A cookie meter and a DeviceID meter behave identically until the reader tries to game them. Then they diverge:
| Reader action | Cookie / VisitorID meter | Per-DeviceID meter |
|---|---|---|
| Reads another article | Count goes up | Count goes up |
| Clears cookies | Count resets to 0 | Same DeviceID, count holds |
| Opens an incognito window | Count resets to 0 | Same DeviceID, count holds |
| Switches networks or uses a VPN | Often resets to 0 | Same DeviceID, count holds |
| Switches to a different browser | New count | New DeviceID, new count (see limits below) |
CookieID is minted in the browser and lost the moment cookies are cleared; the VisitorID is built partly from that cookie, so it resets too. The DeviceID is the durable, browser-bound handle, and metering on it closes the reset loophole. An IP is no steadier: a VPN, proxy, or mobile carrier hands the same reader a fresh address on demand. ShieldLabs returns the identity; your code owns the wall.
This is a metering policy, not a fraud verdict. A reader hitting the wall after their free views is doing nothing wrong, they have simply read what the plan allows. The DeviceID lets you count honestly across cookie resets; the limit and what the wall says are yours.
Stop paywall bypass
Read the durableDeviceID off the webhook for every article view. The rule your code applies: increment a per-DeviceID counter, compare it against your free limit, and show the wall once a device passes the limit. The outcome is a meter the reader cannot reset by clearing cookies, opening incognito, or rotating their IP, because all three keep the same DeviceID.
Build it
Create a ShieldLabs account and get your keys
Sign up for free and get 5,000 identifications, or log in if you already have an account. Register the domain you want to identify visitors on, then open the Keys page. Use the Public Key to initialize the snippet in the browser, and keep your server-side credentials on your backend: the Private API Key authenticates the History API, and each webhook endpoint has its own
whsec_… signing secret. See Keys.Identify on every article view
Load the snippet on every metered article page and run an anonymous identify. Stash the
requestID so your backend can resolve the DeviceID for the view.article.html
Count per DeviceID and wall at your free limit
Your backend reads the scored result for that Metering reads the DeviceID straight off the webhook, which is free; each
RequestID from the shared waitForScore helper, then bumps the counter for the DeviceID it carries and compares it against your free limit. Because clearing cookies and opening incognito both keep the same DeviceID, the reader cannot zero the count by resetting browser storage.api/meter.js
checkAnonymous call is the one request that decrements your domain balance. If a webhook is dropped (delivery is at-most-once, no retries), waitForScore falls back to a free History API read by request_id, so a dropped webhook does not leak a free view.Fall back for views the DeviceID meter cannot cover
Two cases have no usable DeviceID, and pretending otherwise leaks free views:
- Scripts off — the snippet is present but JavaScript is unavailable. The view comes back with the all-zero
device_idand scores 90 or higher, anddetection_flags.javascript_disabledistruewith theJavaScript Disabledsignal. Use that flag, not just the all-zero DeviceID, to route the view to your cookie or IP counter. - Snippet never loads — no snapshot is posted, so no webhook arrives and
waitForScorereturns null.
local_ip.ip) over the public public_ip.ip when present. A VPN hands out a fresh public IP on demand — exactly the reset a per-IP cap is meant to slow — but the real network IP often reveals the underlying network behind it. When detection_flags.ip_mismatch is true the public IP is masked, so an public_ip cap alone is weak there.The other limit is by design: the DeviceID is browser-bound, so the same reader on Chrome and on Firefox is two DeviceIDs, and a determined reader can earn fresh free views by switching browsers or machines. That is a far higher bar than clearing cookies. To raise it further, pair the per-DeviceID counter with a per-IP cap and a soft cookie meter, and require a free account for continued access once any of them trips.| Reset attempt | Per-DeviceID meter result |
|---|---|
| Clear cookies | Blocked, count holds |
| Incognito or private window | Blocked, count holds |
| New network or VPN | Blocked, count holds |
| Different browser, same machine | New count (browser-bound DeviceID) |
| Different device | New count (new DeviceID) |
| Scripts off (snippet present) | All-zero DeviceID, scores 90+, fall back to cookie or IP |
| Snippet never loads | No webhook arrives, fall back to cookie or IP |
Test it
Confirm the meter holds before you enforce. Read past your free limit in a normal window, then try the resets a reader would try:Clear cookies and reload
Read enough articles to trip the wall, clear cookies, and reload. The
device_id in the webhook is the same one, so your per-device count keeps climbing instead of resetting to zero.Open the article in incognito
Open the same page in a private window. The
cookie_id and visitor_id change, but the device_id returns identical — proof your meter keys off the durable handle.Do not test with an automated client or a scripts-off fetch — those return the all-zero
device_id and exercise the cookie/IP fallback, not the device meter. Test as a real reader resetting their own browser.Recommended starting policy
A guide, not a rule. The right limit depends on your content and your conversion goals.| Situation | Suggested action |
|---|---|
| Device under the free limit | Serve the article, increment the count |
| Device at or over the free limit | Show the paywall |
| All-zero DeviceID (scripts off / blocked) | Meter by cookie or IP for this view, do not serve free forever |
| Many all-zero views from one IP | Treat as one reader with scripts off, apply the IP cap |
| Same reader across two browsers | Accept as the browser-bound limit, or add a per-IP cap to slow it |
Where to go next
To understand why the DeviceID holds through cookie clears and incognito while the VisitorID does not, read Identification, and the anonymity signals reference covers the masking flags that travel alongside it. The exactdevice_id field and the rest of the scored body live in the webhook payload your meter reads. The Billing page covers the per-request model.
The same durable DeviceID powers neighboring playbooks: Ban Enforcement keys a banlist on the device a returning reader cannot wipe, and Returning Visitor recognizes the device across sessions. To grade where your readers come from, Measure Traffic Quality scores each acquisition source by its anonymous-traffic share.