Every fraud check has two error rates, and only one of them is visible. You will notice the fraudsters who get through, because they cost you money in ways that show up on a dashboard. You will never notice the real customers you turned away, because they simply leave. That asymmetry is why fraud systems drift toward being too strict over time.
If your fraud provider is slow, down, or you have hit a quota, the signup must still work. A fraud check that takes signups offline during an outage has cost you more than the fraud it was bought to stop — and unlike fraud, it hits every legitimate user at once rather than a few bad ones.
let trust = { verdict: "allow", scored: false }; try { trust = await lc.scoreUser({ ip, email }); } catch (err) { log.warn("fraud check unavailable", err); // proceed anyway }
Allow and block is a false choice, and it forces every uncertain case into one of two wrong answers. A third path — verify — resolves almost everything: a one-time code costs a real person a few seconds and costs an automated attack the whole run.
Store the score and the reasons for every signup you challenged or refused, then actually look through them weekly. This is the only way false positives become visible. If you find a category of real customer in there — a country, an email provider, a VPN — that is a rule to loosen, not a coincidence.
Run the checks and record the verdicts without acting on them for a week or two. You will see what proportion of your normal, healthy traffic would have been challenged before you inconvenience anybody. If that number surprises you, better to learn it from a log than from your support inbox.
Every check here has a cost in real customers. These are the ones that actually bite — several because we shipped them ourselves.
Paste any IP, email, phone or domain into the playground — no signup. Or get a free API key for 1,000 lookups a month, no card.