Skip to main content
Signup farms exist for one payoff: the reward. The same person spins up fresh accounts to claim a signup bonus, burn through a coupon code, or restart a free trial. The catch happens not when the account is born but when it reaches for the reward, so this tutorial lives at the redemption endpoint. Joining accounts at registration is a separate job the signup tutorial covers. Wire that up for the create-account moment and treat this page as the reward-time gate on top of it.

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 ties every redemption to the account behind it and to everything that account is linked to. Pass the account’s hashed User HID and ShieldLabs links it to the devices, visitors and public and local IPs it uses. The Device ID holds through cleared cookies, incognito mode and IP changes, so ten “new” customers on one machine resolve to one Device ID with ten linked accounts. ShieldLabs detects Multi-accounting on your users directly, at Medium or High confidence, and makes it available in the analytics dashboard, the API and webhooks. Underneath, each redemption is one identification: a Risk Score from 0 to 100 with every risk signal named and weighted, so a VPN, proxy, Tor, browser automation or an anti-detect browser on the redemption shows up by name. ShieldLabs answers four questions at each redemption, and you choose the action for each case: The signup and redemption stages answer two different questions, and you want both. Score at signup to thin the farm early (the patient farm creates accounts slowly, each clean on its own), then check again at redemption: ten “different” customers redeeming the same coupon from one machine is a shape no single clean signup ever shows.

Gate the redemption

The policy, wired up in “Build it” below: read the account (its worst band and linked devices), the redemption’s risk_score and named signals, and the number of distinct accounts already behind the Device ID and the Local IP. Grant when the account and the redemption are clean and the device is fresh. Require verification when the redemption carries strong risk signals, when the device already carries more accounts than your per-customer cap allows, or when the account has a Multi-accounting event, whether it reached you through the API or webhooks or you reviewed it in the analytics dashboard. The outcome: a farm clearing cookies and rotating VPN exits between accounts collapses to one Device ID and often one Local IP, so the reward holds for review before it is granted, while a genuine first-time customer passes. ShieldLabs stops promo abuse by linking the accounts and scoring each redemption.

Build it

1

Create a ShieldLabs account and get your keys

Start Free with 5,000 identifications, one time, no credit card, or log in. In the analytics dashboard, add the domain you want to protect under Integration > Domains, then open Integration > API keys and copy its keys with the copy button next to each. The Public Key loads the snippet in the browser. Keep the server credentials on your backend: the Private API Key reads the History API, and each webhook endpoint has its own whsec_… signing secret. See API keys and Integration.
2

Wire the signup gate first

Join accounts to the Device ID at registration with the signup tutorial, so the farm is already thinned before it reaches the reward.
3

Identify the redemption

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). Pass a hashed User HID with checkAuthenticatedUser on every signed-in page. Users, account-level risk and all four High-Risk Events are built on it. Within one visit (while a page of your site stays open in the browser, across route changes in a single-page app and across open tabs), checkAnonymous and checkAuthenticatedUser run at most one identification every five minutes for the same user. A call inside that window posts nothing, counts nothing, and its onInitialized handler receives { status: "not_initialized" }. On a multi-page site, a full page load in the only open tab can start a new visit with its own identification. So for the redemption itself, call forceCheckAuthenticatedUser when the user starts filling the redemption form (its first focus): it runs an identification every time, keeps the current Session ID and restarts the five-minute window, so the redemption always posts its own request ID (see Identify signed-in users). onInitialized fires when the check starts, before the snippet has sent the identification, so do not navigate away inside it: store the request ID and let the form submit normally. Pass the account’s hashed id, never a raw email.
redeem.html
4

Read the Risk Score, gate on risk signals

The Risk Score arrives on the webhook. Verify the 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, which falls back to a History API read by request_id and returns null when there is no identification. A missing identification is unverified, never clean. Hold a redemption with strong risk signals here, then carry on to the account and the device in the next steps.
api/redeem.js
Read a high Risk Score together with its named risk signals and the user’s history. A real customer on a corporate VPN can reach the Suspicious band; the signals show why, and you choose the action for each case. Branch on the band, on the signals[].name slugs or on the named detection_flags, never on a label string.
5

Read the account behind the redemption

The redemption is one identification. The account behind it has a history. The shared accountView helper reads the account’s earlier identifications from the History API by user_hid: the worst band across them shows how risky the account has been, and its distinct Device IDs and public IPs are the devices and networks it is linked to. When a Multi-accounting event arrives for the account through the API or webhooks, or when you review it in the analytics dashboard, record it against the account in your own system with its confidence, and this step reads that record.
Read the account
An account whose worst band is Dangerous, or one linked to more devices than a real customer uses, goes to verification before the reward.In the analytics dashboard, the account’s card shows the same view: its band for the selected period, its High-Risk Events, each pill coloured by its confidence, and each linked device, visitor and IP with the band of the identifications it shares with the account. User, device, visitor and IP cards describes each part.
The header of the user card for User HID a91f3c7e5b2d4086 in the analytics dashboard: the Dangerous band pill, a red Multi-accounting pill (High confidence) and the band split of 12 identifications: 10 Trusted, 1 Suspicious, 1 Dangerous.The header of the user card for User HID a91f3c7e5b2d4086 in the analytics dashboard in the dark theme: the Dangerous band pill, a red Multi-accounting pill (High confidence) and the band split of 12 identifications: 10 Trusted, 1 Suspicious, 1 Dangerous.

A user in the analytics dashboard: the worst band of its identifications and a High-Risk Event (red pill: High confidence).

6

Count the accounts behind the device

The Risk Score tells you whether this one redemption looks masked. The number of accounts behind the device is a separate count, and that count gives the farm away. ShieldLabs detects the farm directly as the Multi-accounting High-Risk Event: several accounts run by one person, linked through the devices and network they share, the classic bonus-farm shape. Each detection carries Medium or High confidence, depending on the combination of evidence, on its own axis next to the Risk Score, so an account can be Trusted on every identification and still be multi-accounting. It needs the hashed User HID, so keep passing it as in the steps above.High-Risk Events are available in the analytics dashboard, the API and webhooks, and the previous step acts on the account when one arrives. At the redemption itself, the Risk Score and risk signals of the identification remain the input, together with a live count: read the History API by device_id and count distinct accounts. Count accounts per local_ip.ip in your own store from the webhook, since the History API has no Local IP search. local_ip is the Local IP: the address the browser itself reports, which can differ from the public IP behind a VPN or proxy.
Read a device's history
Gate the reward on the account and the device
History API reads never count against your included identifications. For high-volume flows, keep your own counters (redemptions per Device ID and per local_ip.ip from the webhook) as the fast path, and reserve live History reads for the rewards that are expensive to give away by mistake. The History API searches by public IP (ip) and has no Local IP search, so the Local IP count always comes from your own store.
A person using several separate browsers shows up as several devices: another browser is another Device ID. A count on local_ip.ip from your own webhook store closes that gap: ten accounts claiming through one Local IP is a strong shape even when each reports a different device, and the Multi-accounting event links accounts through the network they share. Weigh both alongside your own per-code or per-campaign redemption caps.The analytics dashboard shows the same count: open a Device ID from Analytics, and Linked accounts lists every account seen on it, each with the band of its identifications on that device.
The device card for Device ID d290f1ee-6c54-4b01-90e6-d701748f0851 in the analytics dashboard: band Dangerous, 14 identifications, and Linked accounts open with 6 accounts: 3 Dangerous, 1 Suspicious and 2 Trusted.The device card for Device ID d290f1ee-6c54-4b01-90e6-d701748f0851 in the analytics dashboard in the dark theme: band Dangerous, 14 identifications, and Linked accounts open with 6 accounts: 3 Dangerous, 1 Suspicious and 2 Trusted.

One Device ID in the analytics dashboard, with every account linked to it.

7

Decide and tune

Grant, verify or deny in your backend, per the policy table below. Start in logging-only mode, watch how real redemptions distribute, then raise friction where the data justifies it.

Test it

You do not need a real farm to see this work. Claim the reward once in your normal browser and note the device_id on the webhook. Then play the farm: clear cookies or open a private window, 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-account count on that device climbs with each run, which is exactly the count your handler gates on. A second browser gets its own Device ID, which the Local IP count covers. Switching networks or toggling a VPN adds the matching risk signals to the redemption without changing the Device ID. Then search Analytics in the analytics dashboard for that Device ID and open it: Linked accounts lists every account you used in the test, each with the band of its identifications on that device. The three bands are defined in Risk Scoring, and the per-band playbook lives in Acting on results. Mapped to a reward gate, with the account and the device count layered on top: You choose the action for each case, and your per-code or per-campaign redemption caps sit alongside these as a second, simpler backstop.

Next: Acting on results

The full per-band decision playbook, including signal-aware decisioning and how to combine the Risk Score with specific risk signals.