All articles Fundamentals

Writing Your First Fraud Rules: Fields, Operators and Priority

Prynt makes a decision for every identification without any rules: a risk score from 0 to 100 that maps to allow, challenge or block. Rules are how you encode what’s specific to your product. The country you don’t ship to, the free tier that gets farmed, the B2B customers behind corporate VPNs. This post builds a first rule set from scratch, using the real fields and operators, and explains the part that trips most people up: what happens when two rules disagree.

Anatomy of a rule

A rule is one condition and one action:

WHEN <field> <operator> <value> THEN <action>   (priority N)

You create rules in the console, per environment, so a test rule never affects live traffic.

Fields

FieldTypeWhat it is
botbooleanAutomation detected on the device
vpnbooleanVPN egress
virtualMachinebooleanRunning in a VM
tamperingbooleanFingerprint values that contradict each other
incognitobooleanPrivate browsing
formBot, contentSpambooleanScripted form fills, spam content
countrystringIP country code
asnTypestringdatacenter for hosting networks
riskScorenumber0–100 score before rules
velocitynumberIdentifications from this device in the last 5 minutes
accountSharingnumberDistinct accounts on this device in 24 hours
distinctAccounts30dnumberDistinct accounts on this device in 30 days
multiAccountboolean2+ distinct accounts on this device in 30 days
deviceSpreadnumberDistinct devices used by this account in 24 hours

The account-based fields only work if you attach your user ids to events, either with linkedId in identify() or PUT /v1/events/{requestId} after signup. Without that, distinctAccounts30d stays at zero.

Operators

eq, ne, gt, lt, gte, lte and in. Booleans take true or false with eq or ne. Numbers take the comparisons. in takes a comma-separated list, for example country in CA,US,MX.

Actions

  • block refuses the request.
  • challenge asks for more proof: a proof-of-work challenge(), an email code, step-up auth. Your app decides what it means.
  • allow lets the request through.
  • tag adds the rule’s name to the decision’s tags and changes nothing else.

How priority resolves conflicts

Evaluation runs in a fixed order:

  1. Lists. A deny-list match (by visitorId, linkedId, IP or country) blocks. An allow-list match allows. Deny beats allow. Either one skips the rules entirely.
  2. Rules, lowest priority number first:
    • A matching block ends evaluation. Blocked.
    • A matching allow ends evaluation, unless a challenge has already matched.
    • A matching challenge doesn’t end evaluation. It sets a floor: the result will be at least challenge, and a later block can still raise it.
    • A matching tag records its name and evaluation continues.
  3. Score thresholds. If no rule settled it, the risk score decides. In the default configuration, 40 and above challenges and 70 and above blocks.

The challenge-as-floor behavior is deliberate. Without it, a broad rule like “challenge all VPN traffic” at priority 5 would shadow a “block bots” rule at priority 10, and a bot on a VPN would get a challenge it could solve. With the floor, the bot is still blocked. The flip side: once a challenge matches, a later allow can’t clear it. If you want an exception, give the allow rule a lower priority number than the challenge.

A starter rule set

Here’s a set that suits most signup and login flows. Priorities leave gaps so you can insert rules later.

PriorityRuleActionWhy
10bot eq trueblockUnambiguous automation
20tampering eq trueblockSpoofed device, rarely innocent
30distinctAccounts30d gte 4blockClear farming on one device
40country in <countries you don't serve>blockBusiness rule, not a fraud signal
50multiAccount eq truechallengeTwo or more accounts already on the device: verify, don’t refuse
60velocity gt 20challengeBursts from one device
70virtualMachine eq truechallengeCommon in farms, occasionally legitimate
90asnType eq datacentertagWatch it, don’t act on it yet
91vpn eq true (named vpn-watch)tagSame
92incognito eq truetagWeak alone; useful context

A few choices here are worth explaining.

Network signals are tags, not blocks. VPNs, datacenter IPs and private browsing are all used by real people: privacy-minded consumers, remote employees, anyone behind a corporate gateway. They already contribute to the risk score. A rule that blocks on them alone produces more false positives than any other rule you can write.

Multi-accounting gets two rules. multiAccount fires once two distinct accounts have been seen on the device in 30 days, counting the account on the current request if you passed one. At signup, before the new user exists, that means the device already holds two accounts, so the third signup is challenged rather than refused. distinctAccounts30d gte 4 is the hard line: a device that already carries four accounts is blocked on the fifth attempt. That combination lets a person with a work account and a personal one sign up freely, asks a third for verification, and refuses obvious farming. Device-based signup limits covers how to pick those numbers.

The country rule isn’t fraud logic. Keep business rules like geography visibly separate, ideally with names that say so, so nobody mistakes them for risk tuning later.

Combining conditions

Because every rule is a single condition, “block if VPN and a new device and a third account” isn’t one rule. You have three options.

  • Let the score combine them. Each signal adds to riskScore, so a rule like riskScore gte 60 → challenge acts on the combination.
  • Use priority and lists. An allow-list entry for a trusted linkedId, or an allow rule with a lower priority number than your broad challenges, exempts known-good traffic before those rules run. Use this sparingly: an allow skips everything after it.
  • Tag in the rules, decide in code. Your server fetches the event with GET /v1/events/{requestId}, reads risk.tags and smartSignals, and applies any logic you like.
const ev = await prynt.getEvent(requestId);
const tags = ev.risk?.tags || [];
if (ev.decision === 'allow' && tags.includes('vpn-watch') && ev.accountsOnDevice.count >= 2) {
  return requireVerification();
}

Rolling it out

Don’t turn the whole set on at once.

  1. Week 1: everything as tags. Rename each rule to say what it would do (would-block-multi-4) and see what it matches.
  2. Review matches against what you know: support tickets, chargebacks, accounts you banned by hand. Label outcomes so the reason codes on each decision can be measured against reality.
  3. Promote in steps: tag, then challenge, then block, one rule at a time.

Rolling out block mode safely and A/B testing risk rules cover that process in detail, and tuning fraud thresholds covers the score side.

Start with three rules, not ten: block bots, block tampering, challenge the second account on a device. Add the rest as your tags show you where the real abuse is. The docs list every field, and the playground shows which signals your own browser triggers.

Try it free

Prynt is device intelligence with a free tier — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics, powered by a cross-site network. Start free.

Keep reading