← Back to Blog
Ruby on RailsRailsBot DetectionRack MiddlewareBackend SecurityWeb Development

Bot Detection for Ruby on Rails: The Complete 2026 Guide

·13 min read·Device.AI Engineering

Why Rails Apps Are Bot Targets

Ruby on Rails powers millions of web applications—from startup MVPs to enterprise platforms. Your Rails apps handle sensitive operations: user authentication, payment processing, API endpoints, form submissions, and account creation. Without bot detection, attackers can:

  • Brute-force login forms with credential stuffing attacks (stolen username/password pairs)
  • Scrape your Rails API endpoints at scale using Python, Node.js, or Puppeteer
  • Perform fake account registration to resell accounts or commit fraud
  • Spam your platform with auto-generated comments, reviews, or forum posts
  • Abuse your free tier or API quota by making millions of requests
  • Test stolen credit cards on your checkout flow
  • Perform distributed brute-force attacks on your app

Rails developers often focus on building features first, leaving bot detection for later—or not at all. This guide shows you how to add real bot detection to Rails in 5 minutes—with working code, free tier access (1,000 daily verifications), Rack middleware patterns, and production best practices.

How Bot Detection Works

Device.AI's bot detection API accepts an HTTP request context (IP address, headers, request metadata) and returns a bot risk score (0.0 = bot, 1.0 = human). You decide what to do with the score: block, challenge with CAPTCHA, rate-limit, or allow.

Rails bot detection happens server-side via Rack middleware or controller filters. Device.AI analyzes:

  • IP reputation and geolocation
  • Request headers and fingerprints
  • Known bot signatures (User-Agent strings, Puppeteer, Selenium, headless browsers)
  • Request timing and behavioral patterns
  • Proxy detection and VPN identification

Step 1: Get Your Free API Key (1 Minute)

Visit device.ai, click \"Get Free API Key\" (no signup required, no credit card), and copy your key. Your first 1,000 daily verifications are completely free.

You'll receive a key like:

dv_live_abc123def456ghi789jkl

Add it to your config/credentials.yml.enc or environment variables:

DEVICE_AI_API_KEY=dv_live_abc123def456ghi789jkl

Access it in Rails:

api_key = Rails.application.credentials.dig(:device_ai_api_key) || ENV['DEVICE_AI_API_KEY']

Step 2: Install Dependencies

Rails comes with Net::HTTP built-in, or you can use Faraday for easier HTTP calls. We'll use Net::HTTP for this guide (no extra gems needed):

# Gemfile
gem 'httparty'  # Optional: simpler HTTP calls
gem 'rack-attack'  # For rate limiting
bundle install

Step 3: Create a Bot Detection Service

Create a reusable Rails service to verify requests with Device.AI:

# app/services/device_ai_service.rb
class DeviceAIService
  VERIFY_URL = 'https://api.device.ai/v1/verify'.freeze
  DEFAULT_TIMEOUT = 2  # seconds

  def self.verify(request, threshold: 0.5)
    new.verify(request, threshold: threshold)
  end

  def initialize
    @api_key = Rails.application.credentials.dig(:device_ai_api_key) || ENV['DEVICE_AI_API_KEY']
  end

  def verify(request, threshold: 0.5)
    return { score: 0.7, is_bot: false, error: nil } unless @api_key

    begin
      payload = {
        ip: request.remote_ip,
        user_agent: request.user_agent,
        headers: extract_headers(request),
      }

      response = make_request(payload)

      if response.is_a?(Hash) && response['score']
        score = response['score'].to_f
        {
          score: score,
          confidence: response['confidence']&.to_f || 0,
          is_bot: score < threshold,
          error: nil,
        }
      else
        # API error: fail open
        { score: 0.7, is_bot: false, error: response['error'] || 'Unknown error' }
      end
    rescue StandardError => e
      # Network error or timeout: fail open
      Rails.logger.error("DeviceAI verification error: #{e.message}")
      { score: 0.7, is_bot: false, error: e.message }
    end
  end

  private

  def make_request(payload)
    uri = URI(VERIFY_URL)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http.read_timeout = DEFAULT_TIMEOUT

    request = Net::HTTP::Post.new(uri.path)
    request['Content-Type'] = 'application/json'
    request['Authorization'] = "Bearer #{@api_key}"
    request.body = payload.to_json

    response = http.request(request)
    JSON.parse(response.body) if response.body
  rescue Timeout::Error, Errno::ECONNREFUSED => e
    # Timeout: fail open
    { 'score' => 0.7, 'error' => 'Timeout' }
  end

  def extract_headers(request)
    {
      'Accept-Language' => request.headers['HTTP_ACCEPT_LANGUAGE'],
      'Accept-Encoding' => request.headers['HTTP_ACCEPT_ENCODING'],
      'Referer' => request.referer,
    }
  end
end

Step 4: Create a Rack Middleware for Global Bot Detection

Create Rack middleware to verify all requests:

# app/middleware/bot_detection_middleware.rb
class BotDetectionMiddleware
  def initialize(app, options = {})
    @app = app
    @threshold = options[:threshold] || 0.5
    @skip_paths = options[:skip_paths] || ['/health']
  end

  def call(env)
    request = Rack::Request.new(env)

    # Skip verification for certain paths
    if @skip_paths.any? { |path| request.path.start_with?(path) }
      return @app.call(env)
    end

    # Verify device
    result = DeviceAIService.verify(request, threshold: @threshold)

    # Store result in request for use in controllers
    env['device_ai'] = result

    # Block if bot detected
    if result[:is_bot]
      return [
        403,
        { 'Content-Type' => 'application/json' },
        [{ error: 'Bot detected. Request blocked.', score: result[:score] }.to_json],
      ]
    end

    @app.call(env)
  end
end

Register it in your Rails config:

# config/application.rb
class Application < Rails::Application
  config.middleware.use BotDetectionMiddleware, threshold: 0.5, skip_paths: %w[/health /status]
end

Step 5: Create a before_action Filter for Controller-Level Protection

For more fine-grained control, use a Rails filter in your controllers:

# app/controllers/concerns/bot_detectable.rb
module BotDetectable
  extend ActiveSupport::Concern

  included do
    helper_method :device_ai_result
  end

  def verify_device_ai(threshold: 0.5)
    result = DeviceAIService.verify(request, threshold: threshold)
    @device_ai_result = result

    if result[:is_bot]
      render json: { error: 'Bot detected' }, status: :forbidden
    end
  end

  def device_ai_result
    @device_ai_result ||= {}
  end
end

Use it in your controllers:

# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  include BotDetectable
  before_action :verify_device_ai, only: %i[create]

  def create
    # Only bots with score < 0.5 are blocked
    user = User.find_by(email: params[:email])
    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      redirect_to root_path, notice: 'Logged in successfully'
    else
      redirect_to login_path, alert: 'Invalid credentials'
    end
  end
end

Step 6: Option 2 — Use Rack::Attack for Rate Limiting

Combine device.ai bot detection with Rack::Attack for rate limiting:

# config/initializers/rack_attack.rb
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new

# Throttle requests to 10 per minute per IP
Rack::Attack.throttle('requests by ip', limit: 10, period: 60) do |req|
  req.remote_ip
end

# Block obvious bots by User-Agent
Rack::Attack.blocklist('obvious bots') do |req|
  # Block curl, scrapers, headless browsers
  req.user_agent&.match?(/curl|scrapy|wget|python|java|PhantomJS|Headless/i)
end

# Also use device.ai for intelligent bot detection
Rack::Attack.blocklist('device ai bots') do |req|
  result = DeviceAIService.verify(req, threshold: 0.5)
  result[:is_bot]
end

Add to your Rails config to use Rack::Attack middleware:

# config/application.rb
config.middleware.use Rack::Attack

Step 7: Handle Bot Scores with Adaptive Thresholds

Different endpoints need different thresholds. Create specialized filters:

# app/controllers/api/v1/auth_controller.rb
class API::V1::AuthController < ApplicationController
  include BotDetectable

  # Strict threshold for login (prevent credential stuffing)
  before_action { verify_device_ai(threshold: 0.6) }, only: %i[login]

  # Standard threshold for signup
  before_action { verify_device_ai(threshold: 0.5) }, only: %i[register]

  # Loose threshold for comments (allow more humans with uncertainty)
  before_action { verify_device_ai(threshold: 0.3) }, only: %i[post_comment]

  def login
    # Process login
  end

  def register
    # Process signup
  end

  def post_comment
    # Process comment
  end
end

Step 8: Score Interpretation Table

Score RangeClassificationRecommended Action
0.0 - 0.3High confidence botBlock immediately
0.3 - 0.6Uncertain / SuspiciousChallenge (CAPTCHA, email verification)
0.6 - 1.0Likely humanAllow request

Adjust thresholds based on your use case:

  • Login/Authentication: Use 0.6-0.7 threshold to prevent credential stuffing
  • Signup/Registration: Use 0.5 threshold to balance security and user experience
  • Comments/Reviews: Use 0.2-0.3 threshold to avoid false positives on UGC
  • Checkout/Payment: Use 0.7 threshold to prevent payment fraud
  • API Endpoints: Use 0.5 threshold by default

Step 9: Production Best Practices

9.1 Caching Scores

Cache verification results per IP address to reduce API calls and latency:

# app/services/device_ai_service.rb (updated)
class DeviceAIService
  CACHE_TTL = 5.minutes

  def verify(request, threshold: 0.5)
    cache_key = "device_ai:#{request.remote_ip}"
    cached_result = Rails.cache.read(cache_key)

    return cached_result if cached_result.present?

    result = fetch_verification(request, threshold: threshold)
    Rails.cache.write(cache_key, result, expires_in: CACHE_TTL)
    result
  end

  def fetch_verification(request, threshold: 0.5)
    # Original verification logic
  end
end

9.2 Monitoring and Logging

Track bot detection metrics for visibility:

# app/services/device_ai_service.rb (updated)
class DeviceAIService
  def verify(request, threshold: 0.5)
    result = fetch_verification(request, threshold: threshold)

    # Log metrics
    Rails.logger.info("DeviceAI: score=#{result[:score]}, bot=#{result[:is_bot]}, ip=#{request.remote_ip}")

    # Send to monitoring (e.g., StatsD, Datadog)
    if defined?(StatsD)
      StatsD.gauge('device_ai.score', result[:score])
      StatsD.increment('device_ai.bot_detected') if result[:is_bot]
    end

    result
  end
end

9.3 Timeout and Fail-Open Strategy

Bot detection should never break your app. Always fail open:

# Always allow if verification fails
begin
  result = DeviceAIService.verify(request, threshold: @threshold)
  return forbidden_response if result[:is_bot]
rescue StandardError => e
  # Log error but don't block user
  Rails.logger.error("Bot detection failed: #{e.message}")
  # Continue with request
end

9.4 Handling Rate Limit Quota

When you hit the free tier limit (1,000 verifications/day), Device.AI returns HTTP 429. Implement fallback logic:

def make_request(payload)
  # ... existing code ...
  response = http.request(request)

  if response.code == '429'
    # Quota exceeded: use stricter local rate limiting
    Rails.logger.warn('DeviceAI quota exceeded')
    { 'score' => 0.5, 'error' => 'Quota exceeded' }
  else
    JSON.parse(response.body)
  end
end

Step 10: Combining Approaches: A Production Rails Strategy

Here's a production-ready architecture combining all approaches:

# config/application.rb
class Application < Rails::Application
  # 1. Global Rack middleware for coarse-grained protection
  config.middleware.use BotDetectionMiddleware, 
    threshold: 0.5, 
    skip_paths: %w[/health /metrics]

  # 2. Rack::Attack for rate limiting
  config.middleware.use Rack::Attack
end
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  include BotDetectable

  # 3. Controller filter for fine-grained protection
  before_action :verify_device_ai, only: %i[create]
  before_action { verify_device_ai(threshold: 0.6) }, only: %i[create]

  def create
    # Access bot detection result
    score = device_ai_result[:score]
    confidence = device_ai_result[:confidence]

    if score < 0.3
      # Definite bot: block
      return render json: { error: 'Request blocked' }, status: :forbidden
    elsif score < 0.6
      # Uncertain: challenge with CAPTCHA
      session[:require_captcha] = true
      return render 'captcha_challenge'
    end

    # Likely human: process login
    user = User.find_by(email: params[:email])
    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      redirect_to root_path
    else
      redirect_to login_path, alert: 'Invalid credentials'
    end
  end
end

Step 11: Testing Bot Detection

Test your bot detection implementation:

# test/services/device_ai_service_test.rb
require 'test_helper'

class DeviceAIServiceTest < ActiveSupport::TestCase
  test 'identifies obvious bots' do
    request = mock_request(user_agent: 'curl/7.68.0')
    result = DeviceAIService.verify(request, threshold: 0.5)
    assert result[:is_bot]
  end

  test 'allows normal browsers' do
    request = mock_request(user_agent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
    result = DeviceAIService.verify(request, threshold: 0.5)
    assert_not result[:is_bot]
  end

  private

  def mock_request(user_agent: '', ip: '192.168.1.1')
    request = Struct.new(:user_agent, :remote_ip, :referer, :headers).new
    request.user_agent = user_agent
    request.remote_ip = ip
    request.referer = 'https://example.com'
    request.headers = {}
    request
  end
end

Step 12: Handling False Positives

If legitimate users are being blocked, adjust your threshold or create an allowlist:

# app/middleware/bot_detection_middleware.rb (updated)
class BotDetectionMiddleware
  ALLOWLIST_IPS = [
    '192.168.0.0/16',      # Corporate network
    '10.0.0.0/8',          # Internal network
  ].freeze

  def should_skip_verification?(request)
    # Skip for allowlisted IPs
    ALLOWLIST_IPS.any? { |range| ip_in_range?(request.remote_ip, range) }
  end

  private

  def ip_in_range?(ip, range_str)
    IPAddr.new(range_str).include?(IPAddr.new(ip))
  rescue StandardError
    false
  end
end

Pricing and Free Tier

PlanDaily VerificationsPriceBest For
Free1,000$0Development, testing, small apps
Pay-as-you-goUnlimited$0.001 per verificationProduction, scaling
EnterpriseCustomCustom pricingHigh volume, SLAs, priority support

For 1 million daily verifications, pay-as-you-go costs ~$300/month. Upgrade anytime at device.ai/upgrade.

Frequently Asked Questions

Does This Work with Rails 7.0+?

Yes. The code examples use standard Rails patterns (services, concerns, middleware) that work with Rails 6.0+. Adjust gem versions as needed for your Rails version.

Can I Use This with Devise?

Yes. Add the verify_device_ai filter to your Devise controller or create a custom warden strategy:

# config/initializers/devise.rb
warden_manager.after_authentication do |user, auth, opts|
  result = DeviceAIService.verify(auth.request, threshold: 0.5)
  throw :warden, message: :bot_detected if result[:is_bot]
end

Does This Work with Rails API?

Yes. The middleware and service work the same way for Rails API apps. JSON responses are returned automatically.

Can I Integrate with Sentry or Rollbar?

Yes. Capture bot detection errors in your error tracking service:

rescue StandardError => e
  Sentry.capture_exception(e, tags: { feature: 'bot_detection' })
  Rails.logger.error("DeviceAI error: #{e.message}")
end

What's the Performance Impact?

Typical verification latency is 50-150ms. With caching enabled (5 minute TTL), cache hits are <1ms. Total request overhead is negligible.

Common Integration Patterns

Pattern 1: Global Middleware + Cache

# Verify once per IP per 5 minutes
config.middleware.use BotDetectionMiddleware, threshold: 0.5

Pattern 2: API Endpoint Protection

# app/controllers/api/posts_controller.rb
class API::PostsController < ApplicationController
  include BotDetectable
  before_action { verify_device_ai(threshold: 0.5) }, except: %i[index show]
end

Pattern 3: Selective Blocking + Logging

result = DeviceAIService.verify(request, threshold: 0.5)

if result[:score] < 0.3
  # Definite bot: block
  render json: { error: 'Blocked' }, status: :forbidden
elsif result[:score] < 0.6
  # Uncertain: log and allow (with optional CAPTCHA challenge)
  Rails.logger.warn("Suspicious request: score=#{result[:score]}, ip=#{request.remote_ip}")
end

Next Steps

  1. Get a free API key at device.ai (1 minute, no signup)
  2. Copy the DeviceAIService into your Rails project
  3. Add the Rack middleware or controller filters
  4. Test with curl and Puppeteer
  5. Monitor bot detection metrics in production
  6. Adjust thresholds based on your false positive rate
  7. Upgrade to paid plan when you exceed 1,000 daily verifications

Resources

Questions? Contact support@device.ai.

Ready to stop bots?

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

Get Free API Key →