Multi-accounting is one person running several accounts: to claim a sign-up bonus again, to start another free trial, to dodge a ban, or to vote and review as a crowd. Each account has a new email address, so email-based rules never see the pattern. The device usually stays the same.
Fingerly gives every device a visitor ID. Store it with each account, and accounts that share a device are one query away. This guide shows the whole path: identify at sign-up, store the visitor ID, count accounts per device, decide, and link accounts afterwards.
What a visitor ID can and cannot tell you
- In a browser, the same device keeps its visitor ID after cookies and storage are cleared, because the server recognises it rather than reading a value back.
- In an iOS or Android app, the identity is a random install ID. On iOS it survives a reinstall, and on Android a reinstall gets a new visitor ID.
- A visitor ID is scoped to your organization, and a device not seen for 180 days is issued a new one.
- It identifies a device, not a person. A household sharing a tablet is several people behind one visitor ID, and one person on three devices is three.
- A client that gives away almost nothing comes back
identifiable: falsewith a fresh visitor ID each time. Do not count those as one device, and do not count them as unique.
Detect duplicate accounts
Step 1: Identify at sign-up
Call
identify()when the visitor submits the form, with a tag that names the action, and send the request ID with it.import { load } from '@fingerly/web-js' // consent: 'granted' only once you have consent (or another lawful basis) const fingerly = await load({ apiKey: 'fly_pk_development_…', consent: 'granted' }) async function submit() { let requestId: string | undefined try { ({ requestId } = await fingerly.identify({ tag: 'signup' })) } catch { // Carry on: your server treats a missing request ID as missing evidence. } await fetch('/api/signup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, requestId }) }) }Step 2: Store the visitor ID with every account
Take the visitor ID from the stored event your server reads, never from what the client sends. Index it.
ALTER TABLE accounts ADD COLUMN visitor_id text; ALTER TABLE accounts ADD COLUMN signup_request_id text; CREATE INDEX accounts_visitor_id ON accounts (visitor_id);Also store the visitor ID on logins, orders and redemptions. The value comes from joining it with what you already know.
Step 3: Count accounts per device and decide
The server code below uses two helpers from the use-case recipes:
verifiedEvent(requestId, tag), which reads the stored event with a secret key and returns it only when it belongs to the action, is recent and has not been used before, andfired(event, ...groups), which says whether a signal group fired.import { fired, verifiedEvent } from './fingerly.server' export async function signUpDecision(requestId: unknown) { const event = await verifiedEvent(requestId, 'signup') if (!event) return { action: 'verify' as const } const existing = await accounts.countByVisitor(event.visitor_id, { days: 30 }) const banned = await bannedVisitors.has(event.visitor_id) if (banned) return { action: 'review' as const, event } if (existing >= 3) return { action: 'refuse' as const, event } if (fired(event, 'device_farm', 'bot', 'android_emulator', 'ios_simulator', 'cloned_app')) { return { action: 'review' as const, event } } if (existing >= 1 || event.suspect_level !== 'low') return { action: 'verify' as const, event } return { action: 'allow' as const, event } }accountsandbannedVisitorsare your own tables. Fingerly returns the visitor ID, and counting happens in your database.Step 4: Link accounts after the fact
When you find one bad account, its siblings are the other rows with the same visitor ID. To see what a device did in the last 30 days, list its events with a secret key.
SELECT visitor_id, count(*) AS accounts FROM accounts WHERE visitor_id IS NOT NULL GROUP BY visitor_id HAVING count(*) > 1 ORDER BY accounts DESC;Events are readable for 30 days. Keep what you need for longer in your own records. See data retention.
Detect ban evasion
Ban evasion is multi-accounting with a motive: a banned user comes back under a new address. When you ban an account, add its visitor ID to a list of banned visitors. At sign-up, a visitor ID on that list is a strong reason to hold the new account for review.
- Hold for review rather than refuse. The ban may have followed a shared device, and the next person to use it did nothing.
- Do not tell the visitor which check caught them.
- On Android, a banned user who reinstalls gets a new visitor ID, so combine the device with other evidence such as a verified phone number.
A starting policy
| Situation | Action |
|---|---|
| The visitor ID is on your banned list | Create the account on hold for review. |
| The device created 3 or more accounts in 30 days | Refuse, or create the account without its free benefits. |
device_farm, bot, android_emulator, ios_simulator or cloned_app fired | Create the account on hold for review. |
The device already has an account, the level is above low, or the request is unscored | Ask for verification, such as a phone number, before granting benefits. |
| No usable identification | Ask for verification. The device cannot be counted. |
| Otherwise | Allow. |
Pick the limit that fits your product. A family sharing a tablet may reasonably create two accounts, and a device creating twenty is not a household.
Common questions
How do I detect multiple accounts without cookies?
Identify at sign-up with a Fingerly SDK, read the stored event on your server, and store its visitor_id with the account. In a browser the same device keeps its visitor ID after cookies and storage are cleared. In an app the identity is a random install ID.
Does a VPN or a new email address defeat it?
Neither changes the device, so neither by itself changes the visitor ID. The visitor ID comes from the device and not from the address or the email, and a VPN is reported separately as a signal.
What about people who share a device?
They share a visitor ID, so a per-device limit has to leave room for a household. Start with a limit above one, and use verification and review to separate real customers from farmed accounts.
Can I find all accounts that share a device?
Yes, if you stored the visitor ID with each account: group your accounts by it. Fingerly keeps events for 30 days and has no API for counting accounts, so the account-to-device record is yours.
What if the device cannot be identified?
Some clients give away almost nothing and come back identifiable: false. Their fingerprint_suppressed signal fires, and they get a new visitor ID every time, so ask for verification instead of counting them.