← Back to Blog
ReactBot DetectionAPI SecurityJavaScriptClient-Side Security

Bot Detection for React: How to Protect Your App with device.ai

·10 min read·Device.AI Engineering

Why React Apps Need Bot Detection

React applications are prime targets for bot attacks. Your forms collect user data, submissions trigger backend actions, and API calls expose sensitive endpoints. Without bot detection, attackers can:

  • Scrape user data from forms and API endpoints
  • Spam your user-generated content platforms
  • Brute-force login credentials at scale
  • Perform credential-stuffing attacks on checkout flows
  • Abuse your API with fake account registrations or purchases

React runs entirely in the browser, which means your forms and API calls are vulnerable from the start. Unlike Next.js apps with backend middleware, React apps (especially Create React App or Vite-based apps) lack native server-side bot detection. This guide shows you how to add real bot detection to React—with working code for hooks, form protection, API proxy patterns, and server-side verification.

What You'll Need

  • A React project (Create React App, Vite, Next.js, or any bundler)
  • A free device.ai API key (1,000 daily verifications): visit device.ai
  • A simple Node backend or API route for server-side verification (can be Express, Fastify, or Next.js API routes)
  • Five minutes to integrate

How Bot Detection Works in React

Bot detection happens in two phases:

Phase 1: Client-Side Signal Collection

When a user interacts with your React app, device.ai's JavaScript SDK collects behavioral signals:

  • Device fingerprinting: Canvas rendering, WebGL, screen resolution, browser capabilities
  • Automation detection: Detects Puppeteer, Selenium, Playwright, and other bots
  • Behavioral signals: Mouse movement patterns, keystroke timing, scroll behavior

These signals are compressed and sent to your backend (not directly to device.ai's API, for security).

Phase 2: Server-Side Verification

Your backend receives the device signals and calls device.ai's verification endpoint with your API key. device.ai returns a risk score (0.0 to 1.0):

  • 0.0-0.3: High confidence bot. Block immediately.
  • 0.3-0.6: Uncertain. Challenge with CAPTCHA or require additional verification.
  • 0.6-1.0: Likely human. Allow.

Your backend decides what to do with the score: block, challenge, rate-limit, or allow.

Step 1: Get Your API Key (1 minute)

Visit device.ai, click "Get Free API Key", and copy your key. No signup. No credit card. Your first 1,000 daily verifications are free.

Add it to your backend's environment variables:

DEVICE_AI_API_KEY=dv_live_your_key_here

Step 2: Add the Client-Side SDK to React

Add device.ai's JavaScript SDK to your React app's root HTML file (typically public/index.html for Create React App):

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- Add device.ai SDK -->
    <script src="https://js.device.ai/v1/device.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

The device.ai SDK will automatically initialize and start collecting behavioral signals the moment your page loads.

Step 3: Create a useDeviceAI Hook

Create a reusable React hook to collect device signals and send them to your backend for verification:

// hooks/useDeviceAI.ts
import { useCallback, useState } from 'react';

interface VerifyResult {
  score: number; // 0.0 (bot) to 1.0 (human)
  confidence: number; // 0.0 to 1.0
  risk_level: 'low' | 'medium' | 'high';
  is_bot: boolean;
}

interface UseDeviceAIOptions {
  apiEndpoint?: string; // Your backend endpoint
  onVerify?: (result: VerifyResult) => void;
  onError?: (error: Error) => void;
}

export function useDeviceAI(options: UseDeviceAIOptions = {}) {
  const {
    apiEndpoint = '/api/verify-device',
    onVerify,
    onError,
  } = options;

  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [result, setResult] = useState(null);

  const verify = useCallback(async () => {
    setIsLoading(true);
    setError(null);

    try {
      // Get device signals from window.DeviceAI (injected by the SDK)
      const signals = (window as any).DeviceAI?.getSignals?.() || {};

      // Call your backend verification endpoint
      const response = await fetch(apiEndpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ signals }),
      });

      if (!response.ok) {
        throw new Error(
          `Verification failed: ${response.status} ${response.statusText}`
        );
      }

      const verifyResult: VerifyResult = await response.json();
      setResult(verifyResult);
      onVerify?.(verifyResult);

      return verifyResult;
    } catch (err) {
      const error = err instanceof Error ? err : new Error(String(err));
      setError(error);
      onError?.(error);
      throw error;
    } finally {
      setIsLoading(false);
    }
  }, [apiEndpoint, onVerify, onError]);

  return {
    verify,
    isLoading,
    error,
    result,
    isBot: result ? result.score < 0.3 : null,
    isHuman: result ? result.score > 0.6 : null,
  };
}

Step 4: Protect a React Form

Use the hook to protect a form submission:

// components/LoginForm.tsx
import { useState } from 'react';
import { useDeviceAI } from '../hooks/useDeviceAI';

export function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [message, setMessage] = useState(null);
  const [messageType, setMessageType] = useState<'error' | 'success' | null>(null);

  const { verify, isLoading } = useDeviceAI({
    apiEndpoint: '/api/verify-device',
    onVerify: (result) => {
      if (result.score < 0.3) {
        setMessage('Bot detected. Please try again.');
        setMessageType('error');
      } else if (result.score < 0.6) {
        setMessage('Please complete the verification challenge.');
        setMessageType('error');
      } else {
        // Bot check passed, proceed with login
        submitLogin(email, password);
      }
    },
    onError: (error) => {
      console.error('Device verification failed:', error);
      setMessage('Verification failed. Please try again.');
      setMessageType('error');
    },
  });

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!email || !password) {
      setMessage('Please fill in all fields.');
      setMessageType('error');
      return;
    }

    // Step 1: Verify device before submitting login
    await verify();
  };

  const submitLogin = async (email: string, password: string) => {
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
      });

      if (!response.ok) {
        const error = await response.json();
        setMessage(error.message || 'Login failed.');
        setMessageType('error');
        return;
      }

      const result = await response.json();
      setMessage('Login successful!');
      setMessageType('success');
      // Redirect or update app state
    } catch (error) {
      setMessage('An error occurred. Please try again.');
      setMessageType('error');
    }
  };

  return (
    
setEmail(e.target.value)} disabled={isLoading} /> setPassword(e.target.value)} disabled={isLoading} /> {message && (

{message}

)}
); }

Step 5: Build Your Backend Verification Endpoint

Your React app can't directly call device.ai's API (because you can't expose your API key in the browser). Instead, create a backend endpoint that does the verification for you. This is the **proxy pattern** or **Backend for Frontend (BFF)**.

Option A: Express Backend

// backend/routes/verify-device.ts
import express from 'express';

const router = express.Router();

router.post('/api/verify-device', async (req, res) => {
  const { signals } = req.body;

  if (!signals) {
    return res.status(400).json({ error: 'Missing device signals' });
  }

  try {
    // Call device.ai's verification endpoint from your backend
    const verifyResponse = await fetch('https://device.ai/v1/verify', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.DEVICE_AI_API_KEY}`,
      },
      body: JSON.stringify({
        signals,
        // Optional: include IP for additional signals
        ip: req.ip,
      }),
    });

    if (!verifyResponse.ok) {
      throw new Error(
        `device.ai verification failed: ${verifyResponse.status}`
      );
    }

    const result = await verifyResponse.json();

    return res.json({
      score: result.score,
      confidence: result.confidence,
      risk_level:
        result.score < 0.3 ? 'high' : result.score < 0.6 ? 'medium' : 'low',
      is_bot: result.score < 0.5, // Adjust threshold as needed
    });
  } catch (error) {
    console.error('Device verification error:', error);
    // Fail open: return neutral score instead of blocking
    return res.json({
      score: 0.5,
      confidence: 0.2,
      risk_level: 'medium',
      is_bot: false,
    });
  }
});

export default router;

Option B: Next.js API Route (if using Next.js)

// pages/api/verify-device.ts
import type { NextApiRequest, NextApiResponse } from 'next';

type ResponseData = {
  score: number;
  confidence: number;
  risk_level: 'low' | 'medium' | 'high';
  is_bot: boolean;
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({
      score: 0.5,
      confidence: 0,
      risk_level: 'medium',
      is_bot: false,
    });
  }

  const { signals } = req.body;

  if (!signals) {
    return res.status(400).json({
      score: 0.5,
      confidence: 0,
      risk_level: 'medium',
      is_bot: false,
    });
  }

  try {
    const verifyResponse = await fetch('https://device.ai/v1/verify', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.DEVICE_AI_API_KEY}`,
      },
      body: JSON.stringify({
        signals,
        ip: req.headers['x-forwarded-for'] || req.socket.remoteAddress,
      }),
    });

    if (!verifyResponse.ok) {
      throw new Error('device.ai verification failed');
    }

    const result = await verifyResponse.json();

    return res.json({
      score: result.score,
      confidence: result.confidence,
      risk_level:
        result.score < 0.3 ? 'high' : result.score < 0.6 ? 'medium' : 'low',
      is_bot: result.score < 0.5,
    });
  } catch (error) {
    console.error('Device verification error:', error);
    // Fail open
    return res.json({
      score: 0.5,
      confidence: 0.2,
      risk_level: 'medium',
      is_bot: false,
    });
  }
}

Step 6: Protect API Calls with a Custom Fetch Wrapper

Create a wrapper around fetch to automatically verify devices before making API calls:

// utils/verified-fetch.ts
import { useDeviceAI } from '../hooks/useDeviceAI';

export async function verifiedFetch(
  url: string,
  options: RequestInit = {}
) {
  // First, verify the device
  const response = await fetch('/api/verify-device', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ signals: {} }),
  });

  if (!response.ok) {
    throw new Error('Device verification failed');
  }

  const verification = await response.json();

  if (verification.score < 0.3) {
    throw new Error('Bot detected. Request blocked.');
  }

  if (verification.score < 0.6) {
    throw new Error('Device verification required. Please try again.');
  }

  // If verification passed, make the actual API call
  return fetch(url, options);
}

Step 7: Add Bot Detection to Checkout Forms

Checkout flows are high-value targets for bots. Protect them with bot detection:

// components/CheckoutForm.tsx
import { useState } from 'react';
import { useDeviceAI } from '../hooks/useDeviceAI';

export function CheckoutForm() {
  const [items, setItems] = useState([]); // Your cart items
  const [isProcessing, setIsProcessing] = useState(false);
  const [error, setError] = useState(null);

  const { verify, isLoading } = useDeviceAI({
    apiEndpoint: '/api/verify-device',
    onVerify: (result) => {
      if (result.score < 0.3) {
        setError('Bot detected. Please try again later.');
      } else if (result.score < 0.6) {
        setError('Please complete the verification challenge.');
      } else {
        processCheckout();
      }
    },
    onError: (error) => {
      setError(`Verification failed: ${error.message}`);
    },
  });

  const handleCheckout = async (e: React.FormEvent) => {
    e.preventDefault();
    await verify();
  };

  const processCheckout = async () => {
    setIsProcessing(true);
    try {
      const response = await fetch('/api/checkout', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ items }),
      });

      if (!response.ok) {
        setError('Checkout failed. Please try again.');
        return;
      }

      const result = await response.json();
      // Redirect to payment or show success
    } catch (err) {
      setError('An error occurred. Please try again.');
    } finally {
      setIsProcessing(false);
    }
  };

  return (
    
{/* Your checkout form fields */} {error &&

{error}

}
); }

Complete Threshold Guide

Here's how to interpret device.ai scores for different use cases:

Use CaseBlock ThresholdChallenge ThresholdAllow Threshold
Login / Account Takeover< 0.30.3 - 0.6> 0.6
Form Submission (Comments, Reviews)< 0.20.2 - 0.5> 0.5
Checkout / Payment< 0.40.4 - 0.7> 0.7
API Rate Limiting< 0.5N/A> 0.5
Signup / Account Creation< 0.250.25 - 0.55> 0.55

Error Handling and Fallback Strategies

Bot detection should never break your app. Always have fallback strategies:

// Fail-open pattern: if device.ai is down, allow the request
const verifyResponse = await fetch('https://device.ai/v1/verify', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${process.env.DEVICE_AI_API_KEY}`,
  },
  body: JSON.stringify({ signals }),
}).catch(() => {
  // Network error or timeout: fail open
  return new Response(
    JSON.stringify({ score: 0.7, confidence: 0, is_bot: false }),
    { status: 200 }
  );
});

Monitoring and Metrics

Track these metrics to understand your bot traffic:

  • Bot block rate: Percentage of requests with score < 0.3
  • Challenge rate: Percentage of requests with score 0.3-0.6
  • Allow rate: Percentage of requests with score > 0.6
  • Conversion impact: Does bot detection affect legitimate user conversion?
  • False positive rate: Do real users get blocked/challenged?

Log these metrics to your analytics provider or Slack for visibility.

Best Practices for React Bot Detection

  1. Never expose your API key in the browser. Always use a backend proxy (Express, Next.js, or any server).
  2. Verify once per action, not per keystroke. Excessive verification adds latency and consumes your API quota.
  3. Use adaptive thresholds. Checkout flows can be stricter (0.4 threshold) than comments (0.2 threshold).
  4. Monitor false positives. If legitimate users are being challenged or blocked, adjust thresholds.
  5. Fail gracefully. If device.ai is unavailable, allow the request and log the incident.
  6. Combine with other signals. Bot detection is one layer. Use rate limiting, email verification, and CAPTCHAs for defense-in-depth.
  7. Test with automated tools. Use Puppeteer or Playwright to verify that your bot detection actually catches bots.

Frequently Asked Questions

Can I Call device.ai Directly from React?

No. Calling device.ai's API directly from your browser would require exposing your API key, which is a security vulnerability. Always use a backend proxy—it's one extra API call and protects your credentials.

What If I'm Using Vite or a Different Bundler?

The device.ai JavaScript SDK works with any bundler. Just add the <script> tag to your HTML or import the SDK via npm if available. The hook pattern and backend verification logic remain the same.

Can I Use This with React Native or Expo?

Yes. Instead of the browser SDK, use device.ai's mobile API directly from your React Native app. The verification endpoint on your backend remains the same—only the client-side signal collection changes to use native APIs.

How Much Does device.ai Cost?

Free tier: 1,000 verifications per day at no cost. Paid: $0.001 per verification. For 1 million daily verifications, you'd pay ~$300/month.

Does device.ai Support CORS?

Your backend handles the device.ai call, so CORS isn't an issue. Your React app calls your backend (same origin or configured CORS), and your backend calls device.ai.

Next Steps

  1. Get a free API key at device.ai
  2. Copy the useDeviceAI hook into your React project
  3. Create your backend verification endpoint (Express or Next.js)
  4. Add device verification to your first form
  5. Test with Puppeteer or a browser automation tool to verify it works
  6. Monitor bot detection metrics and adjust thresholds based on your false positive rate

Resources

Questions? Contact support@device.ai or check our docs.

Ready to stop bots?

Get a free API key instantly. No signup, no credit card.

Get Free API Key →