Bot Signups and Datacenter Abuse: Detecting Non-Human Traffic at Registration
April 29, 2026
If you run a SaaS product with a free tier, you have a bot problem. You may not know it yet, but somewhere in your user database, there are hundreds or thousands of accounts that were never operated by a human. They were created by scripts running on cloud servers, using throwaway email addresses, for the sole purpose of exploiting your product.
Bot signups are one of the most persistent and expensive problems in SaaS. They inflate your metrics, drain your free tier resources, pollute your analytics, and create downstream issues that compound over time. The good news: the signals that separate bots from real users are well understood, and you can detect them at the moment of registration.
The Bot Signup Problem
Automated account creation affects every product with a public signup form. The motivations vary:
- Free tier abuse. Bots create dozens or hundreds of accounts to multiply free plan limits. If your free plan offers 1,000 API calls per month, a bot operator with 100 accounts gets 100,000 calls for free.
- Credential stuffing infrastructure. Attackers create accounts on your platform to test stolen username/password combinations, using your login flow as a validation tool.
- Spam and content abuse. On platforms with user-generated content, bot accounts post spam, phishing links, or malicious content at scale.
- Metric inflation. Competitors or bad actors inflate your signup numbers to skew your internal data, waste your onboarding email budget, and degrade your deliverability reputation.
- Coupon and promotion abuse. Bots create fresh accounts to repeatedly claim new-user discounts or referral bonuses.
The scale is staggering. Research from Arkose Labs found that over 25% of all login and registration attempts across major platforms are automated. For smaller SaaS products without dedicated fraud teams, the percentage can be even higher because attackers face less resistance.
How Bots Operate
Understanding the mechanics of bot signups helps you choose the right defenses. Modern signup bots follow a predictable pattern.
Datacenter IPs
Nearly all bot traffic originates from cloud hosting providers. AWS, Google Cloud, Azure, DigitalOcean, Hetzner, OVH, and Vultr collectively host the vast majority of automated signup scripts. The economics are simple: a $5/month VPS can run thousands of signup requests per day.
Datacenter IPs are the single strongest signal for non-human registration traffic. While approximately 3-5% of legitimate users may connect through datacenter IPs (corporate VPNs, cloud-based development environments), the remaining 95%+ of datacenter traffic at signup is automated.
Headless Browsers
Basic bots send raw HTTP requests to your signup endpoint. More sophisticated bots use headless browser frameworks like Puppeteer, Playwright, or Selenium to render JavaScript, execute client-side validation, and mimic human-like browser behavior. These tools can:
- Execute JavaScript and pass basic bot detection scripts
- Simulate mouse movements and keystrokes
- Solve simple CAPTCHAs through third-party solving services
- Rotate user agents and browser fingerprints
Disposable Emails
Bots need a unique email for each account. Disposable email services like Mailinator, Guerrilla Mail, and thousands of lesser-known providers offer instant, no-registration inboxes. A bot can generate a unique email address, sign up, receive the verification email, extract the confirmation link, and activate the account, all within seconds.
There are over 50,000 known disposable email domains, and new ones appear daily. Some bot operators also register their own domains in bulk, creating email addresses that are technically valid but exist solely for automated signups.
Registration Velocity
Bots are fast. A single script can create dozens of accounts per minute if your signup flow has no rate limiting or fraud detection. Even with basic rate limiting, attackers distribute their requests across multiple IPs to stay below thresholds.
Detection Signals That Work
Effective bot detection at registration relies on layering multiple signals. No single check catches everything, but the combination is powerful.
Signal 1: IP Type Classification
The most impactful check is classifying the registering IP as residential, datacenter, VPN, proxy, or Tor.
curl -X POST https://api.fidro.io/v1/validate \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "newuser@example.com",
"ip": "198.51.100.42",
"country_code": "US"
}'
The response includes individual flags for each IP type:
{
"risk_score": 45,
"recommendation": "allow",
"checks": {
"datacenter": true,
"vpn": false,
"proxy": false,
"tor": false,
"bad_isp": false,
"bad_ip": false,
"disposable_email": false,
"valid_email": true,
"location_match": true
},
"location": {
"country_code": "US",
"city": "Ashburn",
"isp": "Amazon.com Inc."
}
}
A datacenter IP alone raises the risk score. Combined with other signals, it shifts the recommendation from "allow" to "challenge" or "block."
Signal 2: Email Domain Quality
Disposable email detection is the second most valuable signal. Fidro checks the submitted email against a continuously updated database of 50,000+ known disposable providers and also validates DNS records, MX configuration, and domain legitimacy.
Beyond disposable detection, look at the email domain itself. Newly registered domains (less than 30 days old) used for signups are a strong fraud indicator. Legitimate users overwhelmingly use established email providers.
Signal 3: Registration Velocity
Track how many signups originate from the same IP address within a time window. Legitimate traffic rarely produces more than 2-3 signups from a single IP per hour. Anything above that threshold deserves scrutiny.
// Track registration velocity in your signup controller
$recentSignups = Cache::get("signup_velocity:{$ip}", 0);
if ($recentSignups >= 5) {
// Flag for review or require additional verification
Log::warning('High registration velocity detected', [
'ip' => $ip,
'count' => $recentSignups,
]);
}
Cache::increment("signup_velocity:{$ip}");
Cache::put("signup_velocity:{$ip}", $recentSignups + 1, now()->addHour());
Signal 4: Location Mismatch
When you collect a country or timezone from the user (either explicitly or via browser APIs), compare it against the IP geolocation. A user claiming to be in Germany but connecting from an IP geolocated to a datacenter in Virginia is suspicious. Fidro's location_match check handles this comparison automatically when you include country_code in the request.
Signal 5: Behavioral Patterns
While not detectable via API alone, these client-side signals add another layer:
- Form completion time. Humans take 15-60 seconds to fill out a signup form. Bots complete it in under 2 seconds.
- Mouse and keyboard patterns. Bots often fill fields in perfect sequence with no pauses. Real users click around, hesitate, and make corrections.
- Honeypot fields. Hidden form fields that are invisible to humans but filled in by bots that parse the HTML.
<!-- Honeypot field: hidden from real users, filled by bots -->
<div style="position: absolute; left: -9999px;" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off" />
</div>
// Track form completion time
const formStartTime = Date.now();
document.getElementById('signup-form').addEventListener('submit', (e) => {
const elapsed = (Date.now() - formStartTime) / 1000;
if (elapsed < 3) {
e.preventDefault();
// Too fast for a human, likely automated
console.warn('Signup submitted suspiciously fast:', elapsed, 'seconds');
}
});
Implementation: A Layered Signup Flow
Here is a practical implementation that combines IP intelligence, email validation, and velocity checks at registration.
Node.js / Express Example
const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');
const FIDRO_API_KEY = process.env.FIDRO_API_KEY;
// Basic rate limiting per IP
const signupLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 signups per IP per hour
message: { error: 'Too many signup attempts. Please try again later.' },
});
async function validateSignup(email, ip, countryCode) {
try {
const response = await axios.post(
'https://api.fidro.io/v1/validate',
{
email: email,
ip: ip,
country_code: countryCode,
},
{
headers: {
Authorization: `Bearer ${FIDRO_API_KEY}`,
'Content-Type': 'application/json',
},
timeout: 3000,
}
);
return response.data;
} catch (error) {
// Fail open: if the API is unreachable, allow the signup
// but flag it for manual review
console.error('Fidro API error:', error.message);
return { risk_score: 0, recommendation: 'allow', api_error: true };
}
}
app.post('/api/signup', signupLimiter, async (req, res) => {
const { email, password, country } = req.body;
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
// Step 1: Validate with Fidro
const validation = await validateSignup(email, ip, country);
// Step 2: Act on the recommendation
if (validation.recommendation === 'block') {
return res.status(403).json({
error: 'Signup blocked. Please contact support if you believe this is an error.',
request_id: validation.request_id,
});
}
if (validation.recommendation === 'challenge') {
// Require additional verification (CAPTCHA, phone, etc.)
return res.status(200).json({
action: 'challenge',
message: 'Additional verification required.',
challenge_type: validation.checks?.datacenter ? 'captcha' : 'email_verify',
});
}
// Step 3: Proceed with account creation
// ... your normal signup logic here
// Step 4: Store the risk data for future reference
await storeSignupRiskData(newUser.id, {
risk_score: validation.risk_score,
checks: validation.checks,
ip: ip,
request_id: validation.request_id,
});
return res.status(201).json({ message: 'Account created successfully.' });
});
PHP / Laravel Example
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class SignupController extends Controller
{
public function store(Request $request)
{
$request->validate([
'email' => 'required|email|unique:users',
'password' => 'required|min:8',
]);
$ip = $request->ip();
$email = $request->input('email');
// Check registration velocity
$velocityKey = "signup_velocity:{$ip}";
$recentCount = Cache::get($velocityKey, 0);
if ($recentCount >= 5) {
Log::warning('Registration velocity exceeded', compact('ip', 'email'));
return response()->json([
'error' => 'Too many signup attempts from this network.',
], 429);
}
// Validate with Fidro
$validation = $this->checkFidro($email, $ip);
if ($validation['recommendation'] === 'block') {
Log::info('Signup blocked by Fidro', [
'email' => $email,
'ip' => $ip,
'risk_score' => $validation['risk_score'],
'checks' => $validation['checks'] ?? [],
]);
return response()->json([
'error' => 'Unable to create account. Contact support for assistance.',
], 403);
}
if ($validation['recommendation'] === 'challenge') {
// Store pending signup, require additional verification
return response()->json([
'action' => 'challenge',
'challenge_type' => 'email_verification',
]);
}
// Create the user
$user = User::create([
'email' => $email,
'password' => bcrypt($request->input('password')),
'signup_risk_score' => $validation['risk_score'],
'signup_ip' => $ip,
]);
// Increment velocity counter
Cache::put($velocityKey, $recentCount + 1, now()->addHour());
return response()->json(['message' => 'Account created.'], 201);
}
private function checkFidro(string $email, string $ip): array
{
try {
$response = Http::timeout(3)
->withToken(config('services.fidro.api_key'))
->post('https://api.fidro.io/v1/validate', [
'email' => $email,
'ip' => $ip,
]);
if ($response->successful()) {
return $response->json();
}
} catch (\Exception $e) {
Log::error('Fidro API error', ['message' => $e->getMessage()]);
}
// Fail open if API is unavailable
return ['risk_score' => 0, 'recommendation' => 'allow'];
}
}
Tuning Your Thresholds
The default Fidro risk score thresholds (0-49 allow, 50-79 challenge, 80-100 block) work well as a starting point, but you should tune them based on your data.
Start permissive, tighten over time. For the first two weeks, log all risk scores but only block scores above 80. Review the flagged signups manually to understand your traffic patterns. Then gradually lower the challenge threshold.
Monitor false positives. Track how many challenged users complete verification successfully. If your challenge rate is high but completion is also high, your threshold may be too aggressive. If challenged users rarely complete verification, your threshold is working correctly.
Segment by risk signal. Not all risk signals carry equal weight for your product. A developer tool might see more legitimate datacenter traffic than a consumer app. Adjust your response based on which specific checks triggered:
| Signal Combination | Recommended Action |
|---|---|
| Datacenter IP only | Challenge (CAPTCHA or email verify) |
| Disposable email only | Block |
| Datacenter IP + disposable email | Block |
| VPN + valid email | Allow (common for privacy-conscious users) |
| Tor + any email | Challenge or block depending on product |
| High velocity + any flags | Block |
Beyond Registration: Ongoing Monitoring
Bot detection should not stop at signup. Many sophisticated bots create accounts slowly and patiently, flying under registration-time defenses, then activate abusive behavior days or weeks later.
- Re-validate on key actions. Check the IP and email again when a user requests an API key, upgrades their plan, or accesses a sensitive feature.
- Track usage anomalies. Accounts that immediately hit API rate limits, never log in via a browser, or follow perfectly regular usage patterns are suspicious.
- Periodic re-scoring. Run your existing user emails through Fidro's validation periodically. Domains that become disposable providers after account creation will be caught.
Getting Started
Detecting bot signups starts with two data points you already collect at registration: the email address and the IP address. Fidro's API analyzes both in a single request and returns a risk score with a clear recommendation.
- Sign up for a free Fidro API key to get 200 validations per month.
- Add the validation call to your signup endpoint using the code examples above.
- Start in logging mode: record risk scores for two weeks without blocking anyone.
- Review the data, set your thresholds, and enable enforcement.
For a quick test, try the free IP checker tool to see what Fidro returns for any IP address, or use the email checker to test disposable email detection.