Solve reCAPTCHA Enterprise with Ruby
Use Ruby’s Net::HTTP to create a reCAPTCHA Enterprise task and poll it until it’s ready or failed. The example below calls solvethree’s REST API directly; it doesn’t require a Ruby SDK.
On this page
Configure the site and environment
- 1.
Activate the site profile
Set up the target hostname in the solvethree console and make sure its profile is active for your workspace. Sending a hostname and action doesn’t create a profile. See site profile setup.
- 2.
Set the environment variables
Set
SOLVETHREE_API_KEYto your workspace API key andSOLVETHREE_PROXYto the HTTP or HTTPS proxy URL you’ll also use when submitting the token to the target. Keep the key on the server. - 3.
Change the example inputs
Replace
accounts.example.comwith the target hostname—not a full URL—and replaceloginwith the action required by that target. The request uses oneproxy; don’t also addproxyCandidates. Leave outtypeandurl. OmittingrecaptchaTypeselects the default,enterprise-v3.
Save the example as solve.rb and run it with Ruby after setting those variables. It loads net/http, json, and securerandom; JSON handles the request body and API responses.
Create once, then poll the task ID
require 'net/http'
require 'json'
require 'securerandom'
api = 'https://api.solvethree.com/v1'
input = {site: 'accounts.example.com', action: 'login', proxy: ENV.fetch('SOLVETHREE_PROXY'), requestId: SecureRandom.uuid}
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 180
call = lambda do |method, path, body = nil|
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
raise 'Local deadline reached' if remaining <= 0
uri = URI(api + path)
request = method == :post ? Net::HTTP::Post.new(uri) : Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('SOLVETHREE_API_KEY')
request['Accept'] = 'application/json'
if body
request['Content-Type'] = 'application/json'
request.body = JSON.generate(body)
end
# Net::HTTP's own proxy configuration is separate from input[:proxy].
http = Net::HTTP.new(uri.hostname, uri.port, nil)
http.use_ssl = uri.scheme == 'https'
http.open_timeout = [10, remaining].min
http.read_timeout = [25, remaining].min
http.write_timeout = [25, remaining].min
http.max_retries = 0
response = http.request(request)
raise "API HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
end
task_id = nil
begin
result = call.call(:post, '/tasks?waitMs=0', input)
task_id = result.fetch('id')
while %w[pending solving].include?(result.fetch('status'))
result = call.call(:get, '/tasks/' + URI.encode_www_form_component(task_id) + '?waitMs=15000')
sleep(1) if %w[pending solving].include?(result.fetch('status'))
end
raise "Task failed: #{result['errorCode']}" unless result['status'] == 'ready'
puts "Ready task: #{task_id}"
# Submit result['solution']['token'] with its userAgent/secChUa and original proxy.
rescue StandardError => error
warn "Recovery requestId=#{input[:requestId]} taskId=#{task_id || 'unknown'}"
raise error
endThe script sends one creation request, saves the returned task ID, and retrieves that task while its status is pending or solving. Only ready provides a usable solution. Every response must have a successful HTTP status before its body is parsed as a success payload.
HTTPS is enabled explicitly. Automatic retries are disabled, and redirects are rejected by the HTTP status check rather than followed. The proxy in the task body is for solving; it doesn’t configure the connection from Ruby to the solvethree API.
The shared monotonic deadline gives polling a 180-second local budget. Connection, read, and write timeouts are capped using the time remaining when each request starts. Treat that budget as a polling limit, not a hard cancellation timer for an in-flight request. The Ruby Net::HTTP documentation describes the individual timeout settings.
Submit the token with its browser identity
The example prints the ready task ID, not the token. Read solution.token from the ready result and put it in the target request’s expected token field. Don’t send the task ID instead.
Use the returned solution.userAgent for the User-Agent header and solution.secChUa for Sec-CH-UA. Submit through the same proxy supplied to this task, to the intended target. Don’t replace the returned identity with an unrelated browser’s headers.
A ready task means solvethree generated a solution. Check the target’s response separately to determine whether it accepted your application request.
Handle failures without creating duplicate tasks
- The script raises an API HTTP error.
Inspect the non-success HTTP response. This is a request-level error, not a terminal task result.
Try this: Diagnose that response before retrying. Don’t repeatedly submit unchanged invalid inputs.
- The task reaches
failed. Inspect both
errorCodeanderrorin the result. The example’s raised message includes onlyerrorCode.Try this: Stop polling that task and address the reported cause. Failed solves are refunded; failure doesn’t necessarily mean insufficient credit.
- Creation times out before a task ID is received.
Keep the recovery
requestIdwritten to standard error. A timeout doesn’t prove that creation failed.Try this: Recover by submitting the identical task request with that same request ID. Don’t simply rerun the script: a fresh run generates a new ID and may create another task.
- Polling reaches the local deadline or loses its connection.
Keep the logged task ID and request ID.
Try this: Retrieve the known task ID before creating another task. A local exception isn’t a terminal task status.
For recovery across process restarts, retain the request ID and original task inputs before sending the POST, then retain the task ID once returned. Reusing a request ID with identical inputs returns the existing task; changing the inputs while keeping that ID causes a conflict. See API error handling for the next diagnostic step.
Sources and API reference
Run your first task.
Start with $5 in workspace credit.