Tor network detection: spot visits from Tor exit nodes

Updated

Tor sends a visit through a chain of volunteer relays, and the site sees the address of the last relay, the exit node. FingerPrint Checker reports whether a visit left the Tor network on every event as signals.ip.tor, and rules can use the tor field. Tor is also used by journalists and privacy-minded people, so treat the signal as evidence for a human check or a review, not as proof of fraud.

What Tor does to a visit

Tor is a network that bounces traffic through several relays so the destination cannot see where it started. Your server sees the address of the exit node, which is shared by many people at once and can be in any country.

For a fraud team this means the IP address stops telling you anything about the person. A country check, a per-network limit and a velocity count all see the exit node, not the visitor.

Why Tor shows up in abuse

People who want a fresh identity for every attempt like Tor because each new circuit gives a new address. It appears in:

  • Repeat free-trial and coupon claims that rotate their address between attempts.
  • Card testing and account sign-ups in bulk.
  • Credential stuffing against login forms.

Read the Tor signal

Every identify is stored as an event, and each event carries signals. signals.ip.tor is true when the visit left through a Tor exit node, false when the check ran and found nothing, and null when it could not run. Unknown is not the same as no, so treat null differently from false.

Read events from your backend with your secret key. The browser or app never sees signals.

// Backend. eventId came back from the browser identify call.
if (!/^ev_[0-9A-HJKMNP-TV-Z]{20}$/.test(eventId)) throw new Error('bad eventId'); // fail closed
const res = await fetch(`https://api.fpchecker.com/v1/events/${eventId}`, {
  headers: { 'X-Secret-Key': process.env.FPCHECKER_SECRET_KEY },
});
if (!res.ok) throw new Error('event lookup failed'); // fail closed
const event = await res.json();
if (event.eventId !== eventId || !(Date.now() - Date.parse(event.createdAt) <= 5 * 60_000)) throw new Error('stale or reused event'); // wrong or old event
if (!(await markEventUsed(eventId))) throw new Error('stale or reused event'); // your store: true only the first time

if (event.signals?.ip?.tor === true) {
  requireHumanCheck(event.visitorId); // your code
}

All signals on every event →

What to do with it

Do not block everyone on Tor. A good default is to ask for a human check on actions that cost you money, such as claiming a trial or redeeming a coupon, and to let plain browsing through. You can also search past events with the tor filter and write a rule on the tor field in the console.

Combine it with other evidence. A Tor visit from a device you have seen before on a normal connection is a different story from a first-time device that also shows automation.

Events API and rules reference →

What it does not tell you

The signal covers visits that leave through an exit node. It does not say who the person is, and it does not cover every way of hiding an address. Browsers that hide their fingerprint on purpose are reported separately as a privacy browser, which is not a risk by itself.

Frequently asked questions

Is every Tor user a fraudster?

No. Many people use Tor for privacy or safety. Use the signal to add a step on risky actions, not to turn people away.

Where do I find the Tor result?

On each event, at signals.ip.tor, through the events API, the console and webhooks. You can also search events with the tor filter.

What does a null result mean?

The check could not run for that visit. It is unknown, not clean, so do not read it as no Tor.

Does the signal change the allow or deny answer?

No. Signals are evidence for your own rules. The decision API works as before.

Try FingerPrint Checker free

Every new account gets 2,000 free checks. No card required to start.

Create a free account