Developer10 min readMarch 17, 2026

Catching Bot Behavior: Post-Signup Detection Techniques That Work

How to detect bots after they sign up by analyzing behavioral signals like mouse movement, typing patterns, page navigation, session analysis, and API usage patterns. Combine pre-signup and post-signup signals for stronger fraud detection.

Most fraud prevention focuses on the signup moment. Is this a real email? Is this a suspicious IP? Is this a known burner domain? But some of the most sophisticated bots pass every signup check and reveal themselves only through what they do next. Post-signup behavior analysis is the second line of defense, and for catching high-quality bots and compromised accounts, it is often the most important one.

This article is a technical guide to detecting bot behavior after account creation. We will cover the behavioral signals that distinguish humans from bots, how to collect and analyze them, and how to combine post-signup signals with the pre-signup validation you are already doing.

Why Post-Signup Detection Matters

Pre-signup validation catches the easy cases: disposable emails, datacenter IPs, known fraud fingerprints. But there is a growing class of fraud that passes all of these checks. Attackers use aged email accounts, residential proxies, and anti-detect browsers to create accounts that look legitimate at signup time. These accounts only reveal their true nature through behavior.

Post-signup detection also catches a category that pre-signup validation simply cannot: compromised legitimate accounts. When a real user's credentials are stolen and their account is taken over, the email is valid, the account history is clean, and the only signal is that the behavior suddenly changes.

Mouse Movement Analysis

Human mouse movement is organic, imprecise, and variable. Bot mouse movement is mechanical, direct, and consistent. The differences are measurable and remarkably reliable.

Key Mouse Metrics

interface MouseMetrics {
  totalDistance: number;
  straightLineDistance: number;
  movementTime: number;
  directionChanges: number;
  averageSpeed: number;
  speedVariance: number;
  pauseCount: number;
  curvature: number;  // ratio of actual path to straight line
}

function analyzeMousePath(events: MouseEvent[]): MouseMetrics {
  let totalDistance = 0;
  let directionChanges = 0;
  let prevAngle = 0;
  const speeds: number[] = [];

  for (let i = 1; i < events.length; i++) {
    const dx = events[i].clientX - events[i - 1].clientX;
    const dy = events[i].clientY - events[i - 1].clientY;
    const distance = Math.sqrt(dx * dx + dy * dy);
    const dt = events[i].timeStamp - events[i - 1].timeStamp;

    totalDistance += distance;
    if (dt > 0) speeds.push(distance / dt);

    const angle = Math.atan2(dy, dx);
    if (i > 1 && Math.abs(angle - prevAngle) > 0.3) {
      directionChanges++;
    }
    prevAngle = angle;
  }

  const first = events[0];
  const last = events[events.length - 1];
  const straightLine = Math.sqrt(
    Math.pow(last.clientX - first.clientX, 2) +
    Math.pow(last.clientY - first.clientY, 2)
  );

  const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length;
  const speedVar = speeds.reduce((sum, s) =>
    sum + Math.pow(s - avgSpeed, 2), 0
  ) / speeds.length;

  return {
    totalDistance,
    straightLineDistance: straightLine,
    movementTime: last.timeStamp - first.timeStamp,
    directionChanges,
    averageSpeed: avgSpeed,
    speedVariance: speedVar,
    pauseCount: speeds.filter(s => s < 0.01).length,
    curvature: straightLine > 0 ? totalDistance / straightLine : 0,
  };
}

What to Look For

  • Curvature ratio close to 1.0: Bots tend to move in straight lines between targets. Humans naturally curve and overshoot. A curvature ratio below 1.1 across multiple interactions is suspicious.
  • Low speed variance: Humans accelerate, decelerate, hesitate, and rush. Bots move at a consistent speed. If the speed variance coefficient is below 0.2, something is off.
  • No micro-movements: Even when a human's mouse appears to be still, there are tiny involuntary movements (tremor). Perfect stillness between actions suggests automation.
  • Teleportation: Mouse position jumping discontinuously (no intermediate points between two distant positions) indicates the mouse is being programmatically positioned rather than physically moved.

Typing Pattern Analysis

Keystroke dynamics are another rich behavioral signal. How someone types is nearly as unique as a fingerprint, and bots have a very hard time replicating natural typing patterns.

Metrics That Matter

interface TypingMetrics {
  averageKeypressInterval: number;
  keypressIntervalVariance: number;
  averageDwellTime: number;   // how long each key is held
  dwellTimeVariance: number;
  errorRate: number;          // backspace/delete frequency
  burstiness: number;         // ratio of fast intervals to slow ones
  wordsPerMinute: number;
}

function analyzeTyping(keyEvents: KeyboardEvent[]): TypingMetrics {
  const intervals: number[] = [];
  const dwellTimes: number[] = [];
  let errorCount = 0;
  let charCount = 0;

  const keydowns = new Map<string, number>();

  for (const event of keyEvents) {
    if (event.type === 'keydown') {
      keydowns.set(event.code, event.timeStamp);
      if (event.key === 'Backspace' || event.key === 'Delete') {
        errorCount++;
      } else if (event.key.length === 1) {
        charCount++;
      }
    }

    if (event.type === 'keyup' && keydowns.has(event.code)) {
      const dwellTime = event.timeStamp - keydowns.get(event.code)!;
      dwellTimes.push(dwellTime);
      keydowns.delete(event.code);
    }
  }

  // Calculate intervals between consecutive keydowns
  const keydownEvents = keyEvents.filter(e => e.type === 'keydown');
  for (let i = 1; i < keydownEvents.length; i++) {
    intervals.push(keydownEvents[i].timeStamp - keydownEvents[i - 1].timeStamp);
  }

  const avgInterval = mean(intervals);
  const avgDwell = mean(dwellTimes);

  return {
    averageKeypressInterval: avgInterval,
    keypressIntervalVariance: variance(intervals),
    averageDwellTime: avgDwell,
    dwellTimeVariance: variance(dwellTimes),
    errorRate: charCount > 0 ? errorCount / charCount : 0,
    burstiness: intervals.filter(i => i < avgInterval * 0.5).length / intervals.length,
    wordsPerMinute: charCount / 5 / ((keyEvents[keyEvents.length - 1].timeStamp - keyEvents[0].timeStamp) / 60000),
  };
}

function mean(arr: number[]): number {
  return arr.reduce((a, b) => a + b, 0) / arr.length;
}

function variance(arr: number[]): number {
  const m = mean(arr);
  return arr.reduce((sum, v) => sum + Math.pow(v - m, 2), 0) / arr.length;
}

Bot vs. Human Typing Signatures

  • Uniform intervals: The biggest giveaway. Bots that inject keystrokes with setTimeout or similar mechanisms produce suspiciously regular timing. Even randomized delays tend to follow a uniform distribution, while human typing follows a more complex pattern with word-boundary pauses, thinking pauses, and burst patterns.
  • No errors: Humans make typos. A user who fills out a 200-character form with zero backspace presses is unusual. An error rate of 0% across multiple form fields is a signal.
  • Inhuman speed: Typing above 150 WPM sustained is extremely rare for humans. Above 200 WPM is effectively impossible for form input.
  • Zero dwell time variance: Humans hold different keys for different durations. The spacebar is typically held longer than letter keys. Shift is held while pressing another key. Bots often produce identical dwell times for every key.

Page Navigation Patterns

How a user navigates through your application tells a story. Legitimate users browse, explore, and follow non-linear paths. Bots follow scripts.

Signals to Track

  • Page visit order: Is the user following an unnatural linear path (signup, then settings, then API key generation, then nothing)? Or are they browsing naturally?
  • Time on page: Bots that scrape content spend milliseconds on each page. Bots that abuse free tiers often navigate directly to the resource they want with no browsing.
  • Scroll behavior: Humans scroll in irregular patterns with variable speed. Bots either do not scroll at all or scroll in perfectly uniform increments.
  • Tab visibility: The Page Visibility API reveals whether the user actually has your page in focus. A "user" who fills out forms while the tab is hidden is likely automated.
interface SessionNavigation {
  pagesVisited: string[];
  timePerPage: number[];       // ms spent on each page
  scrollDepths: number[];      // max scroll depth per page (0-1)
  tabFocusTime: number;        // total time tab was in focus
  tabBlurTime: number;         // total time tab was hidden
  rapidNavigations: number;    // page transitions under 500ms
}

function isNavigationSuspicious(session: SessionNavigation): boolean {
  // Rapid navigation through multiple pages
  if (session.rapidNavigations > session.pagesVisited.length * 0.5) {
    return true;
  }

  // No scrolling on any page
  if (session.scrollDepths.every(d => d < 0.05)) {
    return true;
  }

  // Majority of session spent with tab hidden
  const totalTime = session.tabFocusTime + session.tabBlurTime;
  if (totalTime > 0 && session.tabBlurTime / totalTime > 0.8) {
    return true;
  }

  return false;
}

API Usage Patterns

For SaaS products with APIs, post-signup API usage patterns are a goldmine of fraud signals. Bots that create accounts to abuse free tier API access behave very differently from legitimate developers.

Red Flags in API Usage

  • Immediate API calls: A legitimate developer reads the docs, experiments in a sandbox, and gradually increases usage. An account that generates an API key and starts making calls within seconds of signup is likely automated.
  • Monotonic request patterns: Humans make API calls in bursts with natural pauses. Bots make calls at regular intervals, often right at the rate limit.
  • Single-endpoint focus: Legitimate users hit multiple endpoints. Abuse accounts often target a single high-value endpoint exclusively.
  • Error pattern: Legitimate users make mistakes and adjust. Bots often repeat the exact same error without changing parameters, or they never make errors at all.

Session Anomaly Scoring

Individual behavioral signals are noisy. A legitimate user might type fast, or navigate quickly through pages they have seen before. The power comes from combining multiple behavioral signals into a session-level anomaly score.

interface BehaviorScore {
  mouseScore: number;      // 0-1, higher = more human
  typingScore: number;     // 0-1, higher = more human
  navigationScore: number; // 0-1, higher = more human
  apiScore: number;        // 0-1, higher = more human
  composite: number;       // weighted combination
}

function scoreBehavior(session: SessionData): BehaviorScore {
  const mouseScore = scoreMouseBehavior(session.mouseMetrics);
  const typingScore = scoreTypingBehavior(session.typingMetrics);
  const navigationScore = scoreNavigation(session.navigation);
  const apiScore = scoreAPIUsage(session.apiCalls);

  const composite =
    mouseScore * 0.25 +
    typingScore * 0.30 +
    navigationScore * 0.20 +
    apiScore * 0.25;

  return { mouseScore, typingScore, navigationScore, apiScore, composite };
}

A composite score below 0.3 triggers a review. Below 0.15 triggers automatic account suspension. These thresholds should be tuned based on your specific false positive tolerance.

Combining Pre-Signup and Post-Signup Signals

The real power comes from combining pre-signup validation with post-signup behavioral analysis. Here is how to think about it:

  • High pre-signup score + normal behavior: Legitimate user. No action needed.
  • Low pre-signup score: Block at signup. No post-signup analysis needed.
  • Medium pre-signup score + normal behavior: Probably legitimate. Gradually increase trust over time.
  • Medium pre-signup score + suspicious behavior: This is where post-signup detection earns its keep. Flag the account, require additional verification, or restrict access.
  • High pre-signup score + suspicious behavior: Possible account takeover. Alert the original user and require re-authentication.

For more on the device fingerprinting techniques that power pre-signup detection, see our deep-dive. And if you want to build signup forms that prevent fraud from the start, our guide to building fraud-resistant signup forms covers the front-end side of the equation.

Implementation Considerations

Data Collection Ethics

Behavioral tracking raises privacy concerns. Be transparent about it. Include behavioral analysis in your privacy policy. Collect only what you need for fraud detection. Do not repurpose behavioral data for advertising or user profiling beyond security.

Performance Impact

Behavioral data collection must be lightweight. Do not track every mouse event at full resolution. Sample mouse movements at 50ms intervals instead of every event. Batch behavioral data and send it in periodic updates rather than per-event. The goal is invisible data collection that does not degrade the user experience.

Client-Side vs. Server-Side

Collect behavioral data client-side and analyze it server-side. Client-side analysis can be reverse-engineered and circumvented. Server-side analysis keeps your detection logic private. Send raw behavioral metrics to your server and run the scoring there.

BigShield's Post-Signup Signals

BigShield primarily focuses on signup-time validation, but our SDK collects lightweight behavioral signals during the signup flow itself: form completion time, input field focus patterns, paste detection, and basic mouse metrics. These signals feed into the Tier 1 scoring pipeline and add valuable context to the email validation score.

For full post-signup behavioral analysis, we recommend combining BigShield's signup validation with a dedicated behavioral analytics tool, or building your own using the techniques described above. The combination of strong pre-signup scoring from BigShield plus post-signup behavioral monitoring creates a defense-in-depth approach that catches fraud at every stage. Start with BigShield at bigshield.app.

Ready to stop fake signups?

BigShield validates emails with 20+ signals in under 200ms. Start for free, no credit card required.

Get Started Free

Related Articles