API Tutorials

Solving CAPTCHAs with Perl and CaptchaAI API

Perl remains widely used for system administration, text processing, web scraping, and legacy application maintenance. When Perl scripts encounter CAPTCHAs on web forms or data portals, CaptchaAI's HTTP API integrates through Perl's battle-tested HTTP modules — LWP, HTTP::Tiny, or Mojo::UserAgent.

This guide covers reCAPTCHA v2/v3, Cloudflare Turnstile, and image CAPTCHA solving with production-ready Perl modules and scripts.


Why Perl for CAPTCHA Automation

  • Ubiquitous — installed on virtually every Unix/Linux system
  • Regex powerhouse — extract sitekeys and parse HTML with unmatched regex support
  • CPAN ecosystem — thousands of modules for HTTP, JSON, HTML parsing
  • Text processing — ideal for scraping and data extraction pipelines
  • Legacy integration — extend existing Perl applications with CAPTCHA support

Prerequisites

# Core modules (most are included with Perl)
cpan install LWP::UserAgent
cpan install JSON
cpan install MIME::Base64
cpan install URI::Escape

# Optional
cpan install HTTP::Tiny       # lightweight alternative
cpan install Mojo::UserAgent  # async alternative
cpan install HTML::TreeBuilder # HTML parsing

Method 1: LWP::UserAgent (Standard)

CaptchaSolver Module

package CaptchaSolver;

use strict;
use warnings;
use LWP::UserAgent;
use JSON qw(decode_json);
use URI;
use Carp qw(croak);

sub new {
    my ($class, %args) = @_;
    croak "api_key required" unless $args{api_key};

    my $self = bless {
        api_key       => $args{api_key},
        base_url      => 'https://ocr.captchaai.com',
        poll_interval => $args{poll_interval} || 5,
        max_wait      => $args{max_wait} || 300,
        ua            => LWP::UserAgent->new(
            timeout => 30,
            agent   => 'CaptchaSolver-Perl/1.0',
        ),
    }, $class;

    return $self;
}

sub solve_recaptcha_v2 {
    my ($self, %args) = @_;
    croak "site_url required" unless $args{site_url};
    croak "sitekey required"  unless $args{sitekey};

    my $task_id = $self->_submit(
        method    => 'userrecaptcha',
        googlekey => $args{sitekey},
        pageurl   => $args{site_url},
    );

    return $self->_poll($task_id);
}

sub solve_recaptcha_v3 {
    my ($self, %args) = @_;

    my $task_id = $self->_submit(
        method    => 'userrecaptcha',
        googlekey => $args{sitekey},
        pageurl   => $args{site_url},
        version   => 'v3',
        action    => $args{action} || 'verify',
        min_score => $args{min_score} || 0.7,
    );

    return $self->_poll($task_id);
}

sub solve_turnstile {
    my ($self, %args) = @_;

    my $task_id = $self->_submit(
        method  => 'turnstile',
        key     => $args{sitekey},
        pageurl => $args{site_url},
    );

    return $self->_poll($task_id);
}

sub solve_image {
    my ($self, %args) = @_;
    my $base64;

    if ($args{file}) {
        open my $fh, '<:raw', $args{file}
            or croak "Cannot open $args{file}: $!";
        local $/;
        my $data = <$fh>;
        close $fh;
        require MIME::Base64;
        $base64 = MIME::Base64::encode_base64($data, '');
    } elsif ($args{base64}) {
        $base64 = $args{base64};
    } else {
        croak "file or base64 required";
    }

    my $task_id = $self->_submit(
        method => 'base64',
        body   => $base64,
    );

    return $self->_poll($task_id);
}

sub check_balance {
    my ($self) = @_;

    my $uri = URI->new("$self->{base_url}/res.php");
    $uri->query_form(
        key    => $self->{api_key},
        action => 'getbalance',
        json   => 1,
    );

    my $response = $self->{ua}->get($uri);
    croak "HTTP error: " . $response->status_line unless $response->is_success;

    my $data = decode_json($response->decoded_content);
    return $data->{request} + 0;
}

sub _submit {
    my ($self, %params) = @_;

    $params{key}  = $self->{api_key};
    $params{json} = 1;

    my $response = $self->{ua}->post(
        "$self->{base_url}/in.php",
        Content => \%params,
    );

    croak "HTTP error: " . $response->status_line unless $response->is_success;

    my $data = decode_json($response->decoded_content);
    croak "Submit failed: $data->{request}" unless $data->{status} == 1;

    return $data->{request};
}

sub _poll {
    my ($self, $task_id) = @_;
    my $elapsed = 0;

    while ($elapsed < $self->{max_wait}) {
        sleep $self->{poll_interval};
        $elapsed += $self->{poll_interval};

        my $uri = URI->new("$self->{base_url}/res.php");
        $uri->query_form(
            key    => $self->{api_key},
            action => 'get',
            id     => $task_id,
            json   => 1,
        );

        my $response = $self->{ua}->get($uri);
        next unless $response->is_success;

        my $data = decode_json($response->decoded_content);
        next if $data->{request} eq 'CAPCHA_NOT_READY';
        croak "Solve failed: $data->{request}" unless $data->{status} == 1;

        return $data->{request};
    }

    croak "Timeout: CAPTCHA not solved within $self->{max_wait} seconds";
}

1;

Usage

#!/usr/bin/perl
use strict;
use warnings;
use lib '.';
use CaptchaSolver;

my $solver = CaptchaSolver->new(api_key => 'YOUR_API_KEY');

# Check balance
my $balance = $solver->check_balance();
printf "Balance: \$%.2f\n", $balance;

# Solve reCAPTCHA v2
my $token = $solver->solve_recaptcha_v2(
    site_url => 'https://example.com/login',
    sitekey  => '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-',
);
printf "Token: %.50s...\n", $token;

# Solve Turnstile
my $turnstile = $solver->solve_turnstile(
    site_url => 'https://example.com/form',
    sitekey  => '0x4AAAAAAAB5...',
);
printf "Turnstile: %.50s...\n", $turnstile;

# Solve image
my $text = $solver->solve_image(file => 'captcha.png');
print "Image text: $text\n";

Method 2: HTTP::Tiny (Lightweight)

use strict;
use warnings;
use HTTP::Tiny;
use JSON qw(decode_json);
use URI::Escape qw(uri_escape);

my $http = HTTP::Tiny->new(timeout => 30);
my $api_key = 'YOUR_API_KEY';
my $base_url = 'https://ocr.captchaai.com';

sub solve_recaptcha_v2_tiny {
    my ($site_url, $sitekey) = @_;

    # Submit
    my $body = join '&',
        "key=$api_key",
        "json=1",
        "method=userrecaptcha",
        "googlekey=" . uri_escape($sitekey),
        "pageurl=" . uri_escape($site_url);

    my $resp = $http->request('POST', "$base_url/in.php", {
        content => $body,
        headers => { 'content-type' => 'application/x-www-form-urlencoded' },
    });

    die "Submit HTTP error: $resp->{status}" unless $resp->{success};
    my $data = decode_json($resp->{content});
    die "Submit: $data->{request}" unless $data->{status} == 1;

    my $task_id = $data->{request};

    # Poll
    for (1..60) {
        sleep 5;
        my $poll = $http->get(
            "$base_url/res.php?key=$api_key&action=get&id=$task_id&json=1"
        );
        next unless $poll->{success};
        my $result = decode_json($poll->{content});
        next if $result->{request} eq 'CAPCHA_NOT_READY';
        die "Solve: $result->{request}" unless $result->{status} == 1;
        return $result->{request};
    }

    die "Timeout";
}

my $token = solve_recaptcha_v2_tiny(
    'https://example.com/login',
    '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-'
);
print "Token: $token\n";

Sitekey Extraction

use HTML::TreeBuilder;
use LWP::UserAgent;

sub extract_sitekey {
    my ($url) = @_;
    my $ua = LWP::UserAgent->new();
    my $response = $ua->get($url);
    die "Fetch failed" unless $response->is_success;

    my $tree = HTML::TreeBuilder->new_from_content($response->decoded_content);

    # reCAPTCHA
    my @recaptcha = $tree->look_down('data-sitekey', qr/.+/);
    if (@recaptcha) {
        return {
            type => 'recaptcha',
            key  => $recaptcha[0]->attr('data-sitekey'),
        };
    }

    # Turnstile
    my @turnstile = $tree->look_down(class => qr/cf-turnstile/, 'data-sitekey', qr/.+/);
    if (@turnstile) {
        return {
            type => 'turnstile',
            key  => $turnstile[0]->attr('data-sitekey'),
        };
    }

    # Script regex fallback
    my $html = $response->decoded_content;
    if ($html =~ /sitekey['":\s]+['"]([A-Za-z0-9_-]{20,})['"]/) {
        return { type => 'unknown', key => $1 };
    }

    return undef;
}

Submitting Forms with Solved Tokens

sub submit_form_with_token {
    my ($url, $token, %form_data) = @_;

    $form_data{'g-recaptcha-response'} = $token;

    my $ua = LWP::UserAgent->new();
    my $response = $ua->post($url, \%form_data);

    return {
        code    => $response->code,
        content => $response->decoded_content,
        success => $response->is_success,
    };
}

# Usage
my $token = $solver->solve_recaptcha_v2(
    site_url => 'https://example.com/login',
    sitekey  => 'SITEKEY',
);

my $result = submit_form_with_token(
    'https://example.com/login',
    $token,
    username => 'user@example.com',
    password => 'password',
);

print "Login: $result->{code}\n";

Parallel Solving with Threads

use threads;
use threads::shared;

my @results :shared;
my $solver = CaptchaSolver->new(api_key => 'YOUR_API_KEY');

my @tasks = (
    { url => 'https://site-a.com', key => 'KEY_A' },
    { url => 'https://site-b.com', key => 'KEY_B' },
    { url => 'https://site-c.com', key => 'KEY_C' },
);

my @threads;
for my $task (@tasks) {
    push @threads, threads->create(sub {
        my $t = shift;
        eval {
            my $s = CaptchaSolver->new(api_key => 'YOUR_API_KEY');
            my $token = $s->solve_recaptcha_v2(
                site_url => $t->{url},
                sitekey  => $t->{key},
            );
            lock(@results);
            push @results, "$t->{url}: " . substr($token, 0, 50) . "...";
        };
        if ($@) {
            lock(@results);
            push @results, "$t->{url}: ERROR: $@";
        }
    }, $task);
}

$_->join() for @threads;
print "$_\n" for @results;

Error Handling with Retry

sub solve_with_retry {
    my ($solver, %args) = @_;
    my $max_retries = delete $args{max_retries} || 3;
    my $type = delete $args{type} || 'recaptcha_v2';

    my @retryable = qw(
        ERROR_NO_SLOT_AVAILABLE
        ERROR_CAPTCHA_UNSOLVABLE
    );

    for my $attempt (0 .. $max_retries) {
        if ($attempt > 0) {
            my $delay = 2**$attempt + int(rand(3));
            warn "Retry $attempt/$max_retries after ${delay}s\n";
            sleep $delay;
        }

        my $token = eval {
            if ($type eq 'turnstile') {
                return $solver->solve_turnstile(%args);
            } else {
                return $solver->solve_recaptcha_v2(%args);
            }
        };

        return $token unless $@;

        my $error = $@;
        my $is_retryable = grep { $error =~ /\Q$_\E/ } @retryable;

        die $error unless $is_retryable;
    }

    die "Max retries exceeded";
}

# Usage
my $token = solve_with_retry(
    $solver,
    type     => 'recaptcha_v2',
    site_url => 'https://example.com',
    sitekey  => 'SITEKEY',
    max_retries => 3,
);

One-Liner for Quick Solves

# Solve reCAPTCHA v2 from the command line
perl -MLWP::UserAgent -MJSON -e '
    my $ua = LWP::UserAgent->new;
    my $key = $ENV{CAPTCHAAI_KEY};
    my $r = $ua->post("https://ocr.captchaai.com/in.php", {
        key => $key, json => 1, method => "userrecaptcha",
        googlekey => $ARGV[0], pageurl => $ARGV[1]
    });
    my $tid = decode_json($r->content)->{request};
    for (1..60) { sleep 5;
        my $p = $ua->get("https://ocr.captchaai.com/res.php?key=$key&action=get&id=$tid&json=1");
        my $d = decode_json($p->content);
        next if $d->{request} eq "CAPCHA_NOT_READY";
        print $d->{request}; last;
    }
' "SITEKEY" "https://example.com"

Troubleshooting

Error Cause Fix
ERROR_WRONG_USER_KEY Invalid API key Verify key at dashboard
ERROR_ZERO_BALANCE No funds Top up account
Can't locate LWP/UserAgent.pm Module not installed cpan install LWP::UserAgent
SSL connect attempt failed SSL cert issue Install LWP::Protocol::https and Mozilla::CA
500 Can't connect Network/DNS issue Check connectivity
Malformed JSON Non-JSON response Check URL and API status

FAQ

Does CaptchaAI have a Perl module on CPAN?

CaptchaAI uses a REST API. The module shown here works with standard CPAN HTTP libraries — no special SDK needed.

Which HTTP module should I use?

Use LWP::UserAgent for full-featured scripts, HTTP::Tiny for lightweight scripts with no non-core dependencies, or Mojo::UserAgent for async workflows.

Can I use this with CGI or Dancer2?

Yes. Wrap the solver in a service class and call it from your web application's controller.

Does Perl threading work for parallel solves?

Perl threads work but are heavyweight. For parallel solving, consider forking with Parallel::ForkManager or using async I/O with Mojo::IOLoop.



Extend your Perl scripts with CAPTCHA solving — get your API key and integrate today.

Discussions (0)

No comments yet.

Related Posts

Tutorials CAPTCHA Solving Fallback Chains
Implement fallback chains for CAPTCHA solving with Captcha AI.

Implement fallback chains for CAPTCHA solving with Captcha AI. Cascade through solver methods, proxy pools, an...

Automation Python reCAPTCHA v2
Apr 06, 2026
API Tutorials CaptchaAI API Latency Optimization: Faster Solves
Reduce CAPTCHA solve latency with Captcha AI by optimizing poll intervals, connection pooling, prefetching, and proxy selection.

Reduce CAPTCHA solve latency with Captcha AI by optimizing poll intervals, connection pooling, prefetching, an...

Automation Python reCAPTCHA v2
Feb 27, 2026
API Tutorials Building a Python Wrapper Library for CaptchaAI API
Build a reusable Python wrapper library for the Captcha AI API with type hints, retry logic, context managers, and support for CAPTCHA types.

Build a reusable Python wrapper library for the Captcha AI API with type hints, retry logic, context managers,...

Automation Python reCAPTCHA v2
Jan 31, 2026
API Tutorials Solving CAPTCHAs with Swift and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Swift using Captcha AI's HTTP API with URLSession, async/await, and Alamofire.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Swift using Captcha AI's HTTP API with...

Automation reCAPTCHA v2 Cloudflare Turnstile
Apr 05, 2026
API Tutorials Solving CAPTCHAs with Kotlin and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Kotlin using Captcha AI's HTTP API with Ok Http, Ktor client, and coroutines.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Kotlin using Captcha AI's HTTP API with...

Automation reCAPTCHA v2 Cloudflare Turnstile
Mar 06, 2026
API Tutorials Solving CAPTCHAs with Ruby and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Ruby using Captcha AI's HTTP API with net/http, Faraday, and HTTParty.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Ruby using Captcha AI's HTTP API with n...

Automation reCAPTCHA v2 Cloudflare Turnstile
Mar 17, 2026
API Tutorials Solving CAPTCHAs with Scala and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Scala using Captcha AI's HTTP API with sttp, Akka HTTP, and Scala Futures.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Scala using Captcha AI's HTTP API with...

Automation reCAPTCHA v2 Cloudflare Turnstile
Feb 08, 2026
API Tutorials Solving CAPTCHAs with Rust and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Rust using Captcha AI's HTTP API with reqwest, tokio async runtime, and serde.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Rust using Captcha AI's HTTP API with r...

Automation reCAPTCHA v2 Cloudflare Turnstile
Jan 24, 2026
API Tutorials Solving CAPTCHAs with Dart for Flutter Applications
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Dart using Captcha AI's HTTP API with the http package, dio, and Flutter integration.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Dart using Captcha AI's HTTP API with t...

Automation reCAPTCHA v2 Cloudflare Turnstile
Mar 11, 2026
API Tutorials Type-Safe CaptchaAI Client with TypeScript Generics
Build a type-safe Captcha AI API client in Type Script using generics, discriminated unions, and builder patterns for CAPTCHA types.

Build a type-safe Captcha AI API client in Type Script using generics, discriminated unions, and builder patte...

Automation reCAPTCHA v2 Cloudflare Turnstile
Feb 05, 2026
API Tutorials How to Solve reCAPTCHA v2 Callback Using API
how to solve re CAPTCHA v 2 callback implementations using Captcha AI API.

Learn how to solve re CAPTCHA v 2 callback implementations using Captcha AI API. Detect the callback function,...

Automation reCAPTCHA v2 Webhooks
Mar 01, 2026
API Tutorials Solve GeeTest v3 CAPTCHA with Python and CaptchaAI
Step-by-step Python tutorial for solving Gee Test v 3 slide puzzle CAPTCHAs using the Captcha AI API.

Step-by-step Python tutorial for solving Gee Test v 3 slide puzzle CAPTCHAs using the Captcha AI API. Includes...

Automation Python Testing
Mar 23, 2026
API Tutorials Case-Sensitive CAPTCHA API Parameter Guide
How to use the regsense parameter for case-sensitive CAPTCHA solving with Captcha AI.

How to use the regsense parameter for case-sensitive CAPTCHA solving with Captcha AI. Covers when to use, comm...

Python Web Scraping Image OCR
Apr 09, 2026