What is promo abuse?
Promo abuse is when one person creates many accounts to claim a reward that is meant once per customer — a signup bonus, a first-order coupon, referral credit, or a free trial reset. The accounts look like different customers, but they trace back to the same person behind one machine or one network.How ShieldLabs surfaces it
ShieldLabs resolves each redemption to a set of identifiers: the per-claim RequestID, the cookie-scoped VisitorID, your hashed UserHID for the account, and the durable, server-derived DeviceID that survives a cookie clear, incognito, and IP rotation. A farm clears cookies and goes incognito between accounts to look new every time, so counting cookies or public IPs lets it right through — the DeviceID is what holds steady and lets your code see how many “different” customers actually share one machine or local network. ShieldLabs also returns a per-request Risk Score (0–100): when a person masks the connection with a VPN, proxy, or Tor, the anonymity signals fire, and the real network IP (local_ip) can reveal the network behind the public public_ip they rotate. The dashboard grades the relationship over time with the Many Accounts on One Device and Many Accounts on One Local IP patterns; your redemption code enforces your per-customer policy.
ShieldLabs gives you four things at each redemption, and your code decides what to do with them:
| Layer | What it answers | Where you read it | Latency |
|---|---|---|---|
| Identification | ”Is this the same device, even after cleared cookies, incognito, or a new IP?” | The durable device_id on the webhook / History API | About a second |
| Anonymity detection | ”Is this redemption masked or anonymous right now?” | The signals array on the webhook / History API | About a second |
| Risk Score | ”How risky is the visit overall, as one 0-100 number?” | risk_score on the webhook / History API | About a second |
| Patterns | ”How many accounts already cluster on this device or local IP?” | Dashboard Patterns + export | Background (~10 min) |
Gate the redemption
The rule your code applies, wired up in## Build it below: read the session score and its signals breakdown for masking, and read the durable DeviceID plus the real network IP (local_ip.ip) to count how many distinct UserHID accounts already claimed off that one machine or local network. Grant when the score is low and the device is fresh; require verification when the session is masked, when the device or local IP is flagged Suspicious or Dangerous, or when the account count crosses your per-customer cap. The outcome: a farm clearing cookies and rotating VPN exits between accounts collapses to one DeviceID and one local_ip, so the reward holds for review before it is granted, while a genuine first-time customer passes.
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.Wire the signup gate first
Join accounts to the DeviceID at registration with the signup tutorial, so the farm is already thinned before it reaches the reward.
Score the redemption session
Add the snippet to the page where the reward is claimed (the cart with the coupon applied, the “start trial” screen, the bonus-claim button). Re-identify on the action itself so you score the session that is redeeming, not a stale page load. Pass the account’s hashed id, never a raw email.
redeem.html
Read the score, gate on masking
The score arrives on the webhook — verify the A high score is not a fraud verdict — a real customer on a corporate proxy, VPN, or privacy browser can land in the High band. Branch on the score band and the named
X-Shield-Signature HMAC, then cache it by request_id. Your endpoint reads it back with the shared waitForScore helper from the Use Case Tutorials, or falls back to a History API read by request_id. Hold a masked session for verification here, then carry on to the account-count check in the next step.api/redeem.js
detection_flags, never on a raw label string; treat masking as one input, not proof.Count the accounts behind the device
The score tells you whether this one session looks masked. It does not tell you how many accounts sit behind the device — and that count off the durable DeviceID is what gives the farm away. Two dashboard Patterns grade exactly that relationship, Suspicious then Dangerous, as the count crosses a threshold in a rolling window:A determined person using several genuinely separate browsers shows up as several devices, since the DeviceID is browser-bound. The local-IP pattern closes that gap: ten accounts claiming through one
- Many Accounts on One Device — one DeviceID linked to many accounts, the classic bonus-farm shape.
- Many Accounts on One Local IP — many accounts redeeming through one local IP (the real network IP,
local_ip.ip), even when each session shows a fresh cookie and a rotated public IP. Catches a farm sitting behind one router or NAT.
device_id (or by ip for the local_ip value) and count distinct user_hid:Read a device's history
Gate the reward on account count
History reads through
account.shieldlabs.ai do not consume request balance. For high-volume redemption flows, lean on the pattern export as your denylist and reserve live device_id reads for the rewards that are expensive to give away by mistake.local_ip is a strong shape even when each reports a different device. Weigh both patterns alongside your own per-code or per-campaign redemption caps.Test it
You do not need a real farm to see this work. Claim the reward once in your normal browser and note thedevice_id on the webhook. Then play the farm: clear cookies, open a private/incognito window, or switch to a second browser profile, and redeem again as a different account. The cookie_id and visitor_id change each time, but the same device_id returns, and the distinct-user_hid count off that device climbs with each run — exactly the count your handler gates on. Switching networks or toggling a VPN should also light up the anonymity signals on the redeeming session without changing the DeviceID.
Recommended starting thresholds
The four bands are defined in Risk Scoring, and the per-band playbook lives in Acting on the Risk Score. Mapped to a reward gate, with the account count layered on top:| Signal at redemption | Suggested reward action |
|---|---|
| Clean / Low score, no pattern flag | Grant the reward |
| Medium score, no pattern flag | Grant, but log and watch the device |
| High score | Require verification before granting |
| Device or local IP flagged Suspicious | Require verification, regardless of the session score |
| Device or local IP flagged Dangerous | Deny the reward and route to review |
| All-zero / missing DeviceID (snippet blocked or JS disabled, score 90+) | Require verification before granting |
Next: Acting on the Risk Score
The full per-band decision playbook, including signal-aware decisioning and how to combine the score with specific signals.