BotID

BotID is available on all plans

Sophisticated bots are designed to mimic real user behavior. They can run JavaScript, solve CAPTCHAs, and navigate interfaces in ways that closely resemble human interactions. Tools like Playwright and Puppeteer automate these sessions, simulating actions from page load to form submission.

These bots do not rely on spoofed headers or patterns that typically trigger rate limits. Instead, they blend in with normal traffic, making detection difficult and mitigation costly.

Vercel BotID is an invisible CAPTCHA that protects against sophisticated bots without showing visible challenges or requiring manual intervention. It adds a protection layer for public, high-value routes, such as checkouts, signups, and APIs, that are common targets for bots imitating real users.

BotID includes a Deep Analysis mode, powered by Kasada. Kasada is a leading bot protection provider trusted by Fortune 500 companies and global enterprises. It delivers advanced bot detection and anti-fraud capabilities.

BotID provides real-time protection against:

  • Automated attacks: Shield your application from credential stuffing, brute force attacks, and other automated threats
  • Data scraping: Prevent unauthorized data extraction and content theft
  • API abuse: Protect your endpoints from excessive automated requests
  • Spam and fraud: Block malicious bots while allowing legitimate traffic through
  • Expensive resources: Prevent bots from consuming expensive infrastructure, bandwidth, compute, or inventory
  • Seamless integration: Works with existing Vercel projects with minimal configuration
  • Customizable protection: Define which paths and endpoints require bot protection
  • Privacy-focused: Respects user privacy while providing robust protection
  • Deep Analysis (Kasada-powered): For the highest level of protection, enable Deep Analyis in your Vercel Dashboard. This leverages Kasada's advanced detection technology to block even the most sophisticated bots.

BotID has two modes:

  • Basic - Ensures valid browser sessions are accessing your sites
  • Deep Analysis - Connects thousands of additional client side signals to further distinguish humans from bots

With a few lines of code, you can run BotID on any endpoint. It operates by:

  • Giving you a clear yes or no response to each request
  • Deploying dynamic detection models based on a deep understanding of bots that validates requests on your server actions and route handlers to ensure only verified traffic reaches your protected endpoints
  • Quickly assessing users without disrupting user sessions

BotID counters the most advanced bots by:

  1. Silently collecting thousands of signals that distinguish human users from bots
  2. Changing detection methods on every page load to prevent reverse engineering and sophisticated bypasses
  3. Streaming attack data to a global machine learning system that improves protection for all customers

Before setting up BotID, ensure you have a JavaScript project deployed on Vercel.

  1. Add BotID to your project:

    pnpm i botid
  2. Use the appropriate configuration method for your framework to set up proxy rewrites. This ensures that ad-blockers, third party scripts, and more won't make BotID any less effective.

    next.config.js
    import { withBotId } from 'botid/next/config';
     
    const nextConfig = {
      // Your existing Next.js config
    };
     
    export default withBotId(nextConfig);
    nuxt.config.ts
    export default defineNuxtConfig({
      modules: ['botid/nuxt'],
    });

    For other frameworks, add the following configuration values to your vercel.json:

    When using vercel.json configuration, these rewrites only apply in production. For local development behavior, see the Local Development Behavior section.

    vercel.json
    {
      "rewrites": [
        {
          "source": "/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/a-4-a/c.js",
          "destination": "https://api.vercel.com/bot-protection/v1/challenge"
        },
        {
          "source": "/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/:path*",
          "destination": "https://api.vercel.com/bot-protection/v1/proxy/:path*"
        }
      ],
      "headers": [
        {
          "source": "/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/:path*",
          "headers": [
            {
              "key": "X-Frame-Options",
              "value": "SAMEORIGIN"
            }
          ]
        }
      ]
    }
  3. Choose the appropriate method for your framework:

    • Next.js 15.3+: Use initBotId() in instrumentation.client.ts for optimal performance
    • Other Next.js: Mount the <BotIdClient/> component in your layout head
    • Other frameworks: Call initBotId() during application initialization

    Next.js 15.3+ (Recommended)

    We recommend using initBotId() in instrumentation-client.ts for better performance in Next.js 15.3+. For earlier versions, use the React component approach.

    instrumentation-client.ts
    import { initBotId } from 'botid/client/core';
     
    // Define the paths that need bot protection.
    // These are paths that are routed to by your app.
    // These can be:
    // - API endpoints (e.g., '/api/checkout')
    // - Server actions invoked from a page (e.g., '/dashboard')
    // - Dynamic routes (e.g., '/api/create/*')
     
    initBotId({
      protect: [
        {
          path: '/api/checkout',
          method: 'POST',
        },
        {
          // Wildcards can be used to expand multiple segments
          // /team/*/activate will match
          // /team/a/activate
          // /team/a/b/activate
          // /team/a/b/c/activate
          // ...
          path: '/team/*/activate',
          method: 'POST',
        },
        {
          // Wildcards can also be used at the end for dynamic routes
          path: '/api/user/*',
          method: 'POST',
        },
      ],
    });

    Next.js < 15.3

    app/layout.tsx
    import { BotIdClient } from 'botid/client';
    import { ReactNode } from 'react';
     
    const protectedRoutes = [
      {
        path: '/api/checkout',
        method: 'POST',
      },
    ];
     
    type RootLayoutProps = {
      children: ReactNode;
    };
     
    export default function RootLayout({ children }: RootLayoutProps) {
      return (
        <html lang="en">
          <head>
            <BotIdClient protect={protectedRoutes} />
          </head>
          <body>{children}</body>
        </html>
      );
    }
    plugins/botid.client.ts
    import { initBotId } from 'botid/client/core';
     
    export default defineNuxtPlugin({
      enforce: 'pre',
      setup() {
        initBotId({
          protect: [{ path: '/api/post-data', method: 'POST' }],
        });
      },
    });
    src/hooks.client.ts
    import { initBotId } from 'botid/client/core';
     
    export function init() {
      initBotId({
        protect: [
          {
            path: '/api/post-data',
            method: 'POST',
          },
        ],
      });
    }
  4. Use checkBotId() on the routes configured in the <BotIdClient/> component.

    Important configuration requirements: - Not adding the protected route to <BotIdClient /> will result in checkBotId() failing. The client side component dictates which requests to attach special headers to for classification purposes. - Local development always returns isBot: false unless you configure the developmentOptions option on checkBotId(). Learn more about local development behavior.

    • Using API routes
    app/api/sensitive/route.ts
    import { checkBotId } from 'botid/server';
    import { NextRequest, NextResponse } from 'next/server';
     
    export async function POST(request: NextRequest) {
      const verification = await checkBotId();
     
      if (verification.isBot) {
        return NextResponse.json({ error: 'Access denied' }, { status: 403 });
      }
     
      const data = await processUserRequest();
     
      return NextResponse.json({ data });
    }
    • Using Server Actions
    app/actions/create-user.ts
    'use server';
     
    import { checkBotId } from 'botid/server';
     
    export async function createUser(formData: FormData) {
      const verification = await checkBotId();
     
      if (verification.isBot) {
        throw new Error('Access denied');
      }
     
      const userData = {
        name: formData.get('name') as string,
        email: formData.get('email') as string,
      };
     
      const user = await saveUser(userData);
      return { success: true, user };
    }

    BotID actively runs JavaScript on page sessions and sends headers to the server. If you test with curl or visit a protected route directly, BotID will block you in production. To effectively test, make a fetch request from a page in your application to the protected route.

  5. Only available on Pro or Enterprise plans

    From the Vercel dashboard

    • Select your Project
    • Click the Firewall tab
    • Click Configure
    • Enable Vercel BotID Deep Analysis
ModePlans AvailablePrice
BasicAll PlansFree
Deep AnalysisPro and Enterprise$1/1000 checkBotId() Deep Analysis calls

Calling the checkBotId() function in your code triggers BotID Deep Analysis charges. Passive page views or requests that don't invoke the checkBotId() function are not charged.

Available in botid@1.4.5 and above

When you need fine-grained control over BotID's detection levels, you can specify advancedOptions to choose between basic and deep analysis modes on a per-route basis. This configuration takes precedence over the project-level BotID settings in your Vercel dashboard.

Important: The checkLevel in both client and server configurations must be identical for each protected route. A mismatch between client and server configurations will cause BotID verification to fail, potentially blocking legitimate traffic or allowing bots through.

In your client-side protection setup, you can specify the check level for each protected path:

initBotId({
  protect: [
    {
      path: '/api/checkout',
      method: 'POST',
      advancedOptions: {
        checkLevel: 'deepAnalysis', // or 'basic'
      },
    },
    {
      path: '/api/contact',
      method: 'POST',
      advancedOptions: {
        checkLevel: 'basic',
      },
    },
  ],
});

In your server-side endpoint that uses checkBotId(), ensure it matches the client-side configuration.

export async function POST(request: NextRequest) {
  const verification = await checkBotId({
    advancedOptions: {
      checkLevel: 'deepAnalysis', // Must match client-side config
    },
  });
 
  if (verification.isBot) {
    return NextResponse.json({ error: 'Access denied' }, { status: 403 });
  }
 
  // Your protected logic here
}

By default, BotID validates that requests come from the same host that serves the BotID challenge. However, if your application architecture separates your frontend and backend domains (e.g., your app is served from vercel.com but your API is on api.vercel.com or vercel-api.com), you'll need to configure extraAllowedHosts.

The extraAllowedHosts parameter in checkBotId() allows you to specify a list of frontend domains that are permitted to send requests to your backend:

app/api/backend/route.ts
export async function POST(request: NextRequest) {
  const verification = await checkBotId({
    advancedOptions: {
      extraAllowedHosts: ['vercel.com', 'app.vercel.com'],
    },
  });
 
  if (verification.isBot) {
    return NextResponse.json({ error: 'Access denied' }, { status: 403 });
  }
 
  // Your protected logic here
}

Only add trusted domains to extraAllowedHosts. Each domain in this list can send requests that will be validated by BotID, so ensure these are domains you control.

Use this configuration when:

  • Your frontend is hosted on a different domain than your API (e.g., myapp.comapi.myapp.com)
  • You have multiple frontend applications that need to access the same protected backend
  • Your architecture uses a separate subdomain for API endpoints

You can combine extraAllowedHosts with other advanced options:

app/api/backend-advanced/route.ts
const verification = await checkBotId({
  advancedOptions: {
    checkLevel: 'deepAnalysis',
    extraAllowedHosts: ['app.example.com', 'dashboard.example.com'],
  },
});

You can add a bypass rule to the Vercel WAF to let through traffic that would have otherwise been detected as a bot by BotID.

You can view BotID checks by selecting BotID on the firewall page dropdown on a project.

Metrics are also available in Observability Plus.

BotID does not support traditional HTML forms that use the action and method attributes, such as:

<form id="contact-form" method="POST" action="/api/contact">
  <!-- form fields -->
  <button type="submit">Send</button>
</form>

Native form submissions bypass this mechanism, preventing BotID from classifying or protecting these requests.

To ensure the necessary headers are attached, handle the form submission in JavaScript and send the request using fetch or XMLHttpRequest, allowing BotID to properly verify the request.

Here's how you can refactor your form to work with BotID:

async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  const formData = new FormData(e.currentTarget);
  const response = await fetch('/api/contact', {
    method: 'POST',
    body: formData,
  });
  const data = await response.json();
  // handle response
}
 
return (
  <form onSubmit={handleSubmit}>
    {/* form fields */}
    <button type="submit">Send</button>
  </form>
);

If you're using Next.js, you can use a server action in your form and use the checkBotId function to verify the request:

'use server';
import { checkBotId } from 'botid/server';
 
export async function submitContact(formData: FormData) {
  const verification = await checkBotId();
  if (verification.isBot) {
    throw new Error('Access denied');
  }
  // process formData
  return { success: true };
}

And in your form component:

'use client';
import { submitContact } from '../actions/contact';
 
export default function ContactForm() {
  async function handleAction(formData: FormData) {
    return submitContact(formData);
  }
 
  return (
    <form action={handleAction}>
      {/* form fields */}
      <button type="submit">Send</button>
    </form>
  );
}

During local development, BotID behaves differently than in production to facilitate testing and development workflows:

  • Default behavior: In development mode, checkBotId() always returns { isBot: false }, allowing all requests to pass through
  • This ensures: Your development workflow isn't interrupted by bot protection while building and testing features

If you need to test BotID's different return values in local development, you can use the developmentBypass option:

app/api/sensitive/route.ts
import { checkBotId } from 'botid/server';
import { NextRequest, NextResponse } from 'next/server';
 
export async function POST(request: NextRequest) {
  const verification = await checkBotId({
    developmentOptions: {
      bypass: 'BAD-BOT', // default: 'HUMAN'
    },
  });
 
  if (verification.isBot) {
    return NextResponse.json({ error: 'Access denied' }, { status: 403 });
  }
 
  // Your protected logic here
}

The developmentOptions option only works in development mode and is ignored in production. In production, BotID always performs real bot detection.

This allows you to:

  • Test your bot handling logic without deploying to production
  • Verify error messages and fallback behaviors
  • Ensure your application correctly handles both human and bot traffic
Last updated on July 22, 2025