API Tutorials

PowerShell + CaptchaAI: Solve CAPTCHA di Windows Automation

Invoke-RestMethod sudah ada di Windows sejak PowerShell 5.1 — cukup itu saja untuk menyelesaikan reCAPTCHA atau Turnstile di skrip yang dijalankan Task Scheduler tengah malam tanpa pengawasan. Tidak perlu Python, tidak perlu modul pihak ketiga: dua permintaan REST ke endpoint HTTP CaptchaAI — kirim task, lalu polling hasil — sudah menutup celah yang bikin browser automation mandek di halaman login atau checkout berproteksi CAPTCHA.

Panduan ini memakai alur empat langkah yang sama di tiap fungsi: kirim task ke in.php, simpan ID task, polling res.php, lalu pakai token. Anda akan mendapat fungsi siap pakai untuk reCAPTCHA v2/v3, Turnstile, dan CAPTCHA gambar, plus satu modul PowerShell lengkap yang bisa langsung diimpor ke skrip automation apa pun.


Yang Perlu Disiapkan

Tidak banyak yang dibutuhkan. Cukup PowerShell 5.1 bawaan Windows atau PowerShell 7+ kalau Anda ingin lintas platform, satu API key CaptchaAI (daftar dan ambil di sini), dan koneksi internet — tidak ada modul eksternal yang perlu di-install, semua fungsi di bawah pakai cmdlet bawaan.


Kenapa PowerShell Layak Dipakai untuk Automasi CAPTCHA

Lima alasan cmdlet bawaan sudah cukup, tanpa toolchain tambahan:

Alasan Kenapa penting
Sudah ada di Windows PowerShell 5.1 terpasang default di Windows 10/11 dan Windows Server, tanpa instalasi
Invoke-RestMethod native Parsing JSON otomatis, tanpa library HTTP eksternal
Terintegrasi Task Scheduler Skrip yang bergantung pada CAPTCHA bisa dijadwalkan langsung dari Windows
Gampang disambung ke pipeline Hasil Solve-RecaptchaV2 atau Solve-Turnstile langsung diteruskan lewat pipe
Tidak terkunci di Windows PowerShell 7+ jalan juga di Linux dan macOS untuk runner CI/CD non-Windows

Pola yang paling umum di lapangan: tim QA internal menjalankan smoke test harian di Windows Server — on-premise maupun instance cloud di kawasan Asia Tenggara seperti AWS ap-southeast-1 (Singapura) atau GCP asia-southeast2 (Jakarta). Task Scheduler memicu skrip tengah malam ke halaman checkout atau formulir pendaftaran yang memakai reCAPTCHA atau Turnstile, lalu mengirim ringkasan hasil ke Slack sebelum jam kerja dimulai.


Fungsi Inti: Kirim dan Polling Task CAPTCHA

Dua fungsi ini jadi fondasi semua contoh berikutnya: satu mengirim task, satu lagi menunggu hasilnya.

Kirim task ke in.php. Submit-CaptchaTask menerima API key dan parameter task dalam bentuk hashtable, lalu mem-POST-nya ke endpoint in.php. Fungsi ini melempar exception kalau status respons bukan 1, jadi kegagalan langsung ketahuan, bukan diam-diam gagal.

function Submit-CaptchaTask {
    param(
        [Parameter(Mandatory)]
        [string]$ApiKey,

        [Parameter(Mandatory)]
        [hashtable]$TaskParams
    )

    $body = @{
        key  = $ApiKey
        json = 1
    } + $TaskParams

    $response = Invoke-RestMethod -Uri "https://ocr.captchaai.com/in.php" `
        -Method Post `
        -Body $body `
        -ContentType "application/x-www-form-urlencoded"

    if ($response.status -ne 1) {
        throw "Submit failed: $($response.request)"
    }

    return $response.request
}

Polling res.php sampai token siap. Get-CaptchaResult polling ke res.php setiap PollIntervalSeconds detik sampai status berubah dari CAPCHA_NOT_READY, atau sampai MaxWaitSeconds habis. Nilai default 5 dan 300 detik di bawah ini aman untuk sebagian besar tipe CAPTCHA.

function Get-CaptchaResult {
    param(
        [Parameter(Mandatory)]
        [string]$ApiKey,

        [Parameter(Mandatory)]
        [string]$TaskId,

        [int]$MaxWaitSeconds = 300,
        [int]$PollIntervalSeconds = 5
    )

    $deadline = (Get-Date).AddSeconds($MaxWaitSeconds)

    while ((Get-Date) -lt $deadline) {
        Start-Sleep -Seconds $PollIntervalSeconds

        $response = Invoke-RestMethod -Uri "https://ocr.captchaai.com/res.php" `
            -Method Get `
            -Body @{
                key    = $ApiKey
                action = "get"
                id     = $TaskId
                json   = 1
            }

        if ($response.request -eq "CAPCHA_NOT_READY") {
            Write-Verbose "Waiting for solution..."
            continue
        }

        if ($response.status -ne 1) {
            throw "Solve failed: $($response.request)"
        }

        return $response.request
    }

    throw "Timeout: CAPTCHA not solved within $MaxWaitSeconds seconds"
}

Menyelesaikan reCAPTCHA v2 dari PowerShell

Solve-RecaptchaV2 membungkus dua fungsi di atas jadi satu pemanggilan. Method API-nya userrecaptcha, dengan googlekey diisi site key target dan pageurl diisi URL halaman yang menampilkan CAPTCHA-nya.

function Solve-RecaptchaV2 {
    param(
        [Parameter(Mandatory)]
        [string]$ApiKey,

        [Parameter(Mandatory)]
        [string]$SiteUrl,

        [Parameter(Mandatory)]
        [string]$SiteKey
    )

    Write-Host "Submitting reCAPTCHA v2 task..."
    $taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
        method    = "userrecaptcha"
        googlekey = $SiteKey
        pageurl   = $SiteUrl
    }
    Write-Host "Task ID: $taskId"

    Write-Host "Polling for solution..."
    $token = Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
    Write-Host "Solved! Token: $($token.Substring(0, [Math]::Min(50, $token.Length)))..."

    return $token
}

# Usage
$apiKey = "YOUR_API_KEY"
$token = Solve-RecaptchaV2 `
    -ApiKey $apiKey `
    -SiteUrl "https://staging.example.com/qa-login" `
    -SiteKey "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"

Menyelesaikan Cloudflare Turnstile

Turnstile memakai method turnstile — parameternya lebih ringkas dari reCAPTCHA karena tidak perlu version atau action.

function Solve-Turnstile {
    param(
        [Parameter(Mandatory)]
        [string]$ApiKey,

        [Parameter(Mandatory)]
        [string]$SiteUrl,

        [Parameter(Mandatory)]
        [string]$SiteKey
    )

    $taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
        method  = "turnstile"
        key     = $SiteKey
        pageurl = $SiteUrl
    }

    return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}

# Usage
$token = Solve-Turnstile `
    -ApiKey "YOUR_API_KEY" `
    -SiteUrl "https://example.com/form" `
    -SiteKey "0x4AAAAAAAB5..."

Menyelesaikan reCAPTCHA v3 dengan Skor Aksi

reCAPTCHA v3 tidak menampilkan tantangan visual, tapi tetap butuh parameter action yang cocok dengan yang didefinisikan di halaman target (verify, login, submit, dst.) supaya skor yang dikembalikan relevan.

function Solve-RecaptchaV3 {
    param(
        [Parameter(Mandatory)]
        [string]$ApiKey,

        [Parameter(Mandatory)]
        [string]$SiteUrl,

        [Parameter(Mandatory)]
        [string]$SiteKey,

        [string]$Action = "verify",
    )

    $taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
        method    = "userrecaptcha"
        googlekey = $SiteKey
        pageurl   = $SiteUrl
        version   = "v3"
        action    = $Action
    }

    return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}

Menyelesaikan CAPTCHA Gambar (OCR)

Untuk CAPTCHA berbasis gambar, kirim gambarnya dalam bentuk base64 lewat method base64. Dua fungsi di bawah menangani dua sumber gambar paling umum: file lokal dan URL langsung.

Dari file lokal

function Solve-ImageCaptcha {
    param(
        [Parameter(Mandatory)]
        [string]$ApiKey,

        [Parameter(Mandatory)]
        [string]$ImagePath
    )

    if (-not (Test-Path $ImagePath)) {
        throw "Image file not found: $ImagePath"
    }

    $imageBytes = [System.IO.File]::ReadAllBytes($ImagePath)
    $base64 = [Convert]::ToBase64String($imageBytes)

    $taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
        method = "base64"
        body   = $base64
    }

    return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}

# Usage
$text = Solve-ImageCaptcha -ApiKey "YOUR_API_KEY" -ImagePath "C:\captcha.png"
Write-Host "CAPTCHA text: $text"

Dari URL gambar

function Solve-ImageCaptchaFromUrl {
    param(
        [Parameter(Mandatory)]
        [string]$ApiKey,

        [Parameter(Mandatory)]
        [string]$ImageUrl
    )

    $imageBytes = (Invoke-WebRequest -Uri $ImageUrl).Content
    $base64 = [Convert]::ToBase64String($imageBytes)

    $taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
        method = "base64"
        body   = $base64
    }

    return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}

Bikin Modul Reusable: CaptchaAI.psm1

Kalau fungsi-fungsi di atas dipakai berulang di banyak skrip, bungkus jadi satu class supaya tidak perlu copy-paste. Simpan sebagai CaptchaAI.psm1:

class CaptchaAISolver {
    [string]$ApiKey
    [string]$BaseUrl = "https://ocr.captchaai.com"
    [int]$PollInterval = 5
    [int]$MaxWait = 300

    CaptchaAISolver([string]$apiKey) {
        $this.ApiKey = $apiKey
    }

    [string] SolveRecaptchaV2([string]$siteUrl, [string]$siteKey) {
        return $this.Solve(@{
            method    = "userrecaptcha"
            googlekey = $siteKey
            pageurl   = $siteUrl
        })
    }

    [string] SolveTurnstile([string]$siteUrl, [string]$siteKey) {
        return $this.Solve(@{
            method  = "turnstile"
            key     = $siteKey
            pageurl = $siteUrl
        })
    }

    [string] SolveImage([string]$imagePath) {
        $bytes = [System.IO.File]::ReadAllBytes($imagePath)
        $base64 = [Convert]::ToBase64String($bytes)
        return $this.Solve(@{
            method = "base64"
            body   = $base64
        })
    }

    [double] GetBalance() {
        $response = Invoke-RestMethod -Uri "$($this.BaseUrl)/res.php" `
            -Body @{ key = $this.ApiKey; action = "getbalance"; json = 1 }
        return [double]$response.request
    }

    hidden [string] Solve([hashtable]$params) {
        $taskId = $this.Submit($params)
        return $this.Poll($taskId)
    }

    hidden [string] Submit([hashtable]$params) {
        $body = @{ key = $this.ApiKey; json = 1 } + $params
        $response = Invoke-RestMethod -Uri "$($this.BaseUrl)/in.php" `
            -Method Post -Body $body
        if ($response.status -ne 1) { throw "Submit: $($response.request)" }
        return $response.request
    }

    hidden [string] Poll([string]$taskId) {
        $deadline = (Get-Date).AddSeconds($this.MaxWait)
        while ((Get-Date) -lt $deadline) {
            Start-Sleep -Seconds $this.PollInterval
            $response = Invoke-RestMethod -Uri "$($this.BaseUrl)/res.php" `
                -Body @{ key = $this.ApiKey; action = "get"; id = $taskId; json = 1 }
            if ($response.request -eq "CAPCHA_NOT_READY") { continue }
            if ($response.status -ne 1) { throw "Solve: $($response.request)" }
            return $response.request
        }
        throw "Timeout"
    }
}

# Export
Export-ModuleMember

Memakai modul di skrip lain

Import modulnya dengan using module, lalu panggil method di class CaptchaAISolver langsung — termasuk GetBalance() untuk cek saldo sebelum menjalankan batch besar.

using module .\CaptchaAI.psm1

$solver = [CaptchaAISolver]::new("YOUR_API_KEY")

# Check balance
$balance = $solver.GetBalance()
Write-Host "Balance: `$$balance"

# Solve reCAPTCHA v2
$token = $solver.SolveRecaptchaV2("https://staging.example.com/qa-login", "SITEKEY")
Write-Host "Token: $($token.Substring(0, 50))..."

Kirim Token ke Form Target

Token hasil solve harus disisipkan ke field yang nama parameternya persis sama dengan yang dibaca server target — untuk reCAPTCHA itu g-recaptcha-response. Salah nama field, form tetap menolak submission meski token-nya valid.

function Submit-FormWithToken {
    param(
        [string]$Url,
        [string]$Token,
        [hashtable]$FormData
    )

    $body = $FormData + @{
        "g-recaptcha-response" = $Token
    }

    $response = Invoke-WebRequest -Uri $Url `
        -Method Post `
        -Body $body `
        -ContentType "application/x-www-form-urlencoded"

    return $response
}

# Usage
$token = Solve-RecaptchaV2 -ApiKey "YOUR_API_KEY" `
    -SiteUrl "https://staging.example.com/qa-login" `
    -SiteKey "SITEKEY"

$result = Submit-FormWithToken `
    -Url "https://staging.example.com/qa-login" `
    -Token $token `
    -FormData @{
        username = "user@example.com"
        password = "password"
    }

Write-Host "Response: $($result.StatusCode)"

Solve CAPTCHA Paralel dengan PowerShell Jobs

Untuk regression test yang menyentuh banyak halaman sekaligus, Start-Job menjalankan beberapa solve secara paralel, bukan satu per satu. Setiap job punya siklus kirim-polling sendiri, jadi total waktu tunggu mendekati waktu solve yang paling lambat, bukan jumlah semuanya.

$apiKey = "YOUR_API_KEY"

$tasks = @(
    @{ Url = "https://site-a.com"; Key = "SITEKEY_A" },
    @{ Url = "https://site-b.com"; Key = "SITEKEY_B" },
    @{ Url = "https://site-c.com"; Key = "SITEKEY_C" }
)

$jobs = $tasks | ForEach-Object {
    $task = $_
    Start-Job -ScriptBlock {
        param($ApiKey, $Url, $SiteKey)

        $taskId = (Invoke-RestMethod -Uri "https://ocr.captchaai.com/in.php" -Method Post -Body @{
            key = $ApiKey; json = 1; method = "userrecaptcha"
            googlekey = $SiteKey; pageurl = $Url
        }).request

        $deadline = (Get-Date).AddSeconds(300)
        while ((Get-Date) -lt $deadline) {
            Start-Sleep -Seconds 5
            $result = Invoke-RestMethod -Uri "https://ocr.captchaai.com/res.php" -Body @{
                key = $ApiKey; action = "get"; id = $taskId; json = 1
            }
            if ($result.request -ne "CAPCHA_NOT_READY" -and $result.status -eq 1) {
                return @{ Url = $Url; Token = $result.request }
            }
        }
        return @{ Url = $Url; Error = "Timeout" }
    } -ArgumentList $apiKey, $task.Url, $task.Key
}

# Wait and collect results
$results = $jobs | Wait-Job | Receive-Job
$results | ForEach-Object {
    if ($_.Token) {
        Write-Host "$($_.Url): $($_.Token.Substring(0, 50))..."
    } else {
        Write-Host "$($_.Url): $($_.Error)" -ForegroundColor Red
    }
}
$jobs | Remove-Job

Semakin banyak job paralel yang jalan bersamaan, semakin banyak thread CaptchaAI yang terpakai — lihat FAQ di bawah soal memilih paket thread yang cocok dengan volume testing tim Anda.


Retry Otomatis dengan Exponential Backoff

Solve-WithRetry hanya mencoba ulang untuk error yang memang layak di-retry — ERROR_NO_SLOT_AVAILABLE dan ERROR_CAPTCHA_UNSOLVABLE. Error lain, misalnya API key yang salah, langsung dilempar sebagai exception. Jeda antar-percobaan naik secara eksponensial (2^attempt detik plus jitter acak) supaya skrip tidak membombardir API saat bermasalah.

function Solve-WithRetry {
    param(
        [Parameter(Mandatory)]
        [string]$ApiKey,

        [Parameter(Mandatory)]
        [hashtable]$TaskParams,

        [int]$MaxRetries = 3
    )

    $retryableErrors = @(
        "ERROR_NO_SLOT_AVAILABLE",
        "ERROR_CAPTCHA_UNSOLVABLE"
    )

    for ($attempt = 0; $attempt -le $MaxRetries; $attempt++) {
        if ($attempt -gt 0) {
            $delay = [Math]::Pow(2, $attempt) + (Get-Random -Maximum 3)
            Write-Host "Retry $attempt/$MaxRetries after $($delay)s..."
            Start-Sleep -Seconds $delay
        }

        try {
            $taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams $TaskParams
            $result = Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
            return $result
        }
        catch {
            $errorMsg = $_.Exception.Message
            $isRetryable = $retryableErrors | Where-Object { $errorMsg -like "*$_*" }

            if (-not $isRetryable -or $attempt -eq $MaxRetries) {
                throw
            }
            Write-Warning "Retryable error: $errorMsg"
        }
    }
}

Jadwalkan lewat Task Scheduler Windows

Register-ScheduledTask mendaftarkan skrip automation supaya jalan otomatis tanpa ada orang yang memicunya — persis pola smoke test harian yang disebutkan di awal panduan ini.

# Create a scheduled task that runs CAPTCHA automation daily
$action = New-ScheduledTaskAction `
    -Execute "powershell.exe" `
    -Argument "-ExecutionPolicy validasi -File C:\Scripts\captcha-automation.ps1"

$trigger = New-ScheduledTaskTrigger -Daily -At "08:00"

Register-ScheduledTask `
    -TaskName "CaptchaAutomation" `
    -Action $action `
    -Trigger $trigger `
    -Description "Run daily CAPTCHA automation with CaptchaAI"

Pertanyaan yang Sering Diajukan

Apakah skrip ini langsung jalan di PowerShell 5.1 bawaan Windows tanpa modul tambahan?

Ya. Semua contoh di panduan ini hanya memakai Invoke-RestMethod dan Invoke-WebRequest, cmdlet bawaan sejak PowerShell 5.1 — tidak ada Install-Module yang perlu dijalankan, baik di Windows 10/11 maupun PowerShell 7+.

Bagaimana cara menjadwalkan solve CAPTCHA ini supaya jalan otomatis tiap hari?

Pakai Register-ScheduledTask seperti pada bagian Task Scheduler di atas. Windows memicu skrip pada jam yang ditentukan tanpa sesi login aktif — cocok untuk smoke test malam hari atau monitoring formulir di luar jam kerja.

Kenapa Invoke-RestMethod gagal dengan error SSL/TLS saat memanggil API CaptchaAI?

Biasanya karena Windows masih memakai versi TLS lama secara default. Tambahkan [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 di baris paling atas skrip, sebelum panggilan API pertama.

Apakah paket thread-based CaptchaAI cocok untuk tim QA yang menjalankan banyak job paralel?

Cocok. CaptchaAI menagih per thread konkuren, bukan per solve — paket BASIC ($15/bulan, 5 thread) cukup untuk pengujian kecil, sementara tim yang rutin menjalankan puluhan Start-Job paralel biasanya naik ke ADVANCE ($90/bulan, 50 thread) tanpa biaya tambahan per CAPTCHA.

Apakah panduan ini juga berlaku untuk hCaptcha atau GeeTest v4?

Belum. hCaptcha dan GeeTest v4 belum didukung CaptchaAI — GeeTest v4 saat ini masih berstatus segera hadir. Endpoint yang sama di panduan ini mendukung reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3, dan CAPTCHA gambar/OCR.


Error Umum dan Cara Memperbaikinya

Sebagian besar error di alur ini berasal dari tiga sumber: kredensial, TLS, atau kebijakan eksekusi skrip Windows.

Kesalahan Penyebab Solusi
ERROR_WRONG_USER_KEY Kunci API tidak valid Verifikasi kunci di dasbor
ERROR_ZERO_BALANCE Tidak ada dana Isi ulang akun
Invoke-RestMethod: SSL/TLS Versi TLS tidak cocok Tambahkan [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
The response content cannot be parsed Respons non-JSON Gunakan Invoke-WebRequest dan parsing secara manual
Kesalahan Execution policy Skrip diblokir Jalankan Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
Cannot convert to double Kesalahan penguraian saldo Gunakan [double]::Parse($response.request)

Panduan Terkait

Kalau automation Anda tidak selalu jalan di Windows, lihat juga panduan otomatisasi shell Bash/cURL dengan CaptchaAI dan solve CAPTCHA dengan C#. Belum punya API key? Mulai dari setup API key dan autentikasi CaptchaAI.


Otomatiskan solve CAPTCHA langsung dari command line Windows — ambil API key CaptchaAI Anda dan mulai bikin skrip automation Anda sendiri.

Komentar dinonaktifkan untuk artikel ini.