EU-Captcha Ruby client
Privacy-first, no-cookie, no-manual-interaction bot protection for Ruby applications. Automatically filters bots, spam, and credential-stuffing attempts without requiring any user interaction.
Requirements
- Ruby 2.7 or later
- No runtime dependencies (uses Ruby's built-in
net/http)
Installation
gem install eu_captcha
Or add it to your Gemfile:
gem 'eu_captcha'
Getting credentials
- Register at app.eu-captcha.eu
- Create a site and copy the sitekey and secret from the dashboard
Quick start
Using a SPA framework? The script tag and
<div>approach below is for server-rendered pages. If you are building with React, Vue, or Angular, use the matching npm package for the frontend widget and continue to use this gem for server-side verification only. See SPA integration guides for details.
Add the widget script to any page that contains a form you want to protect:
<script src="https://cdn.eu-captcha.eu/verify.js" async defer></script>
Place the widget inside your form:
<div class="eu-captcha" data-sitekey="EUCAPTCHA_SITE_KEY"></div>
Verify the submitted token on your server:
require 'eu_captcha'
captcha = EuCaptcha::Client.new(
sitekey: ENV['EUCAPTCHA_SITE_KEY'],
secret: ENV['EUCAPTCHA_SECRET_KEY']
)
result = captcha.validate(
token: params['eu-captcha-response'],
remote_addr: request.ip
)
render_error unless result.success?
Configuration options
All options are passed as keyword arguments to the constructor.
| Option | Type | Default | Description |
|---|---|---|---|
sitekey |
String | — | Required. Public sitekey from the dashboard. |
secret |
String | — | Required. Secret key from the dashboard. Never expose this client-side. |
fail_default |
Boolean | true |
Return value used for both network and token state when the API cannot be reached. true = fail open (allow on error); false = fail closed (deny on error). |
check_cdn_headers |
Boolean | true |
When true, the client IP is resolved from CDN/proxy headers (HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR, HTTP_X_REAL_IP) in the Rack environment before falling back to REMOTE_ADDR. Set to false when not behind a proxy, or when you pass the IP explicitly. |
verify_url |
String | (production URL) | Override the EU-Captcha verify endpoint. Useful for testing. |
credentials_url |
String | (production URL) | Override the EU-Captcha verify-credentials endpoint. |
The result object
validate returns an EuCaptcha::Result with these methods:
| Method | Returns true when… |
|---|---|
success? |
The API was reached and the token is valid. |
success_network? |
The API call completed without a network or transport error. |
success_token? |
The API reported the submitted token as valid. |
train |
Returns true/false/nil (see below). |
Checking both states separately lets you distinguish a user failing the captcha from an API outage:
result = captcha.validate(token: params['eu-captcha-response'], remote_addr: request.ip)
unless result.success_network?
# Could not reach the API — consider logging or alerting
end
unless result.success_token?
# Token was rejected — the submission is likely automated
end
The train method returns true when the API skipped real validation and forced success — typically because the sitekey does not exist, the secret does not match, or the sitekey's protection toggle is disabled. success? already accounts for this flag and returns false in that case. Returns nil on network failure.
Verifying credentials
Use verify_credentials to confirm your sitekey and secret are valid without submitting a client token. Useful for startup or configuration checks:
captcha = EuCaptcha::Client.new(
sitekey: ENV['EUCAPTCHA_SITE_KEY'],
secret: ENV['EUCAPTCHA_SECRET_KEY']
)
unless captcha.verify_credentials
# Credentials are invalid or the API is unreachable — log and alert
end
verify_credentials returns false on any network or API error rather than raising, so it is safe to call during application initialisation.
Rails
Store credentials in config/credentials.yml.enc (or .env + dotenv) and create a client in an initializer:
config/initializers/eu_captcha.rb
EU_CAPTCHA = EuCaptcha::Client.new(
sitekey: Rails.application.credentials.dig(:eucaptcha, :sitekey),
secret: Rails.application.credentials.dig(:eucaptcha, :secret)
)
Controller
class ContactController < ApplicationController
def create
result = EU_CAPTCHA.validate(
token: params['eu-captcha-response'],
remote_addr: request.ip,
user_agent: request.user_agent
)
unless result.success?
render json: { error: 'CAPTCHA verification failed' }, status: :unprocessable_entity
return
end
# process the form...
end
end
request.ip respects Rails' trusted-proxy configuration, so the real visitor IP is forwarded correctly when running behind a CDN or load balancer.
Sinatra / Rack
Pass the Rack environment directly and let the library resolve the client IP automatically:
require 'sinatra'
require 'eu_captcha'
CAPTCHA = EuCaptcha::Client.new(
sitekey: ENV['EUCAPTCHA_SITE_KEY'],
secret: ENV['EUCAPTCHA_SECRET_KEY']
)
post '/contact' do
result = CAPTCHA.validate(
token: params['eu-captcha-response'],
rack_env: env
)
halt 422, 'CAPTCHA verification failed' unless result.success?
# process the form...
end
When rack_env is provided, validate automatically resolves the client IP and User-Agent from the Rack environment, respecting the check_cdn_headers setting.
Further reading
- Full documentation
- Server-side verification reference
- SPA integration guides (React / Next.js, Vue / Nuxt, Angular)
License
BSD 2-Clause. See LICENSE for details.