Retool không có sẵn component giải CAPTCHA, nên cách làm là gọi ra ngoài: khai báo CaptchaAI thành REST API resource, tạo hai query — một query gửi task tới in.php, một query đọc kết quả từ res.php — rồi dùng JavaScript query nối chúng thành vòng polling. Bài này dựng đúng luồng đó với reCAPTCHA v2.
Bài dành cho người đã có app Retool đang chạy và muốn app tự lấy token g-recaptcha-response thay vì để nhân viên vận hành giải tay từng lần.
Luồng chạy: bốn nhịp từ sitekey đến token
- Gửi task kèm
googlekeyvàpageurltớiin.php, nhận về ID task. - Gọi
res.phpmỗi 5 giây cho tới khi có kết quả (polling). - Đọc token trong trường
requestcủa phản hồi. - Gắn token vào tham số
g-recaptcha-responsecủa request gửi form.
Nhịp hai là chỗ Retool khác các môi trường khác: query REST không tự lặp, nên vòng lặp phải nằm trong JavaScript query. Token reCAPTCHA v2 chỉ sống khoảng hai phút, nên hãy gửi form ngay khi có token.
Bạn cần chuẩn bị gì
- Workspace Retool (Cloud hoặc self-hosted) với quyền tạo Resource.
- API key CaptchaAI kèm số dư còn hiệu lực.
sitekeyvàpageurllấy từ mã nguồn trang có reCAPTCHA v2.- Quyền ghi Secrets Store để không phải dán API key vào query.
Bước 1: khai báo CaptchaAI thành REST API resource
Trong Retool, vào Resources → Create New → REST API:
| Trường | Giá trị |
|---|---|
| Tên | CaptchaAI |
| Base URL | https://ocr.captchaai.com |
| Xác thực | Không có (API key gửi kèm dạng query parameter) |
Lưu resource lại; mọi query ở các bước sau đều trỏ về nó.
Base URL này dùng chung cho cả hai query.
Bước 2: query gửi task submitCaptcha
Tạo query submitCaptcha với cấu hình và query parameter sau:
| Mục | Giá trị |
|---|---|
| Resource | CaptchaAI |
| Action Type | GET |
| URL Path | /in.php |
key |
{{secretsStore.CAPTCHAAI_API_KEY}} |
method |
userrecaptcha |
googlekey |
{{sitekeyInput.value}} |
pageurl |
{{pageurlInput.value}} |
json |
1 |
API key nằm trong Secrets Store của Retool (Settings → Secrets) và gọi qua secretsStore; key dán thẳng vào query sẽ lộ với mọi người mở được app. Transformer dưới đây là tùy chọn:
// Parse the response
const data = {{ submitCaptcha.data }};
if (data.status === 1) {
return { taskId: data.request, status: 'submitted' };
}
return { error: data.request, status: 'failed' };
Bước 3: query đọc kết quả pollResult
Tạo query pollResult, cũng trỏ về resource CaptchaAI:
| Mục | Giá trị |
|---|---|
| Action Type | GET |
| URL Path | /res.php |
key |
{{secretsStore.CAPTCHAAI_API_KEY}} |
action |
get |
id |
{{submitCaptcha.data.request}} |
json |
1 |
Khi task chưa xong, API trả về CAPCHA_NOT_READY — trạng thái bình thường chứ không phải lỗi. Transformer sau tách ba tình huống đã giải xong, đang chờ và lỗi thật:
const data = {{ pollResult.data }};
if (data.status === 1) {
return { token: data.request, status: 'solved' };
}
if (data.request === 'CAPCHA_NOT_READY') {
return { status: 'pending' };
}
return { error: data.request, status: 'error' };
Bước 4: dựng vòng polling bằng JavaScript query
JavaScript query solveCaptcha điều phối chu trình gửi và polling:
// solveCaptcha — JavaScript Query
async function solve() {
// Submit the CAPTCHA task
await submitCaptcha.trigger();
const submitResult = submitCaptcha.data;
if (submitResult.status !== 1) {
return { error: submitResult.request, status: 'submit_failed' };
}
const taskId = submitResult.request;
// Wait 15 seconds before first poll
await new Promise(r => setTimeout(r, 15000));
// Poll up to 20 times (100 seconds max)
for (let i = 0; i < 20; i++) {
await pollResult.trigger({
additionalScope: { taskId: taskId }
});
const result = pollResult.data;
if (result.status === 1) {
return { token: result.request, status: 'solved' };
}
if (result.request !== 'CAPCHA_NOT_READY') {
return { error: result.request, status: 'error' };
}
// Wait 5 seconds before next poll
await new Promise(r => setTimeout(r, 5000));
}
return { error: 'Polling timeout', status: 'timeout' };
}
return solve();
Mười lăm giây chờ trước lần polling đầu là có chủ đích: reCAPTCHA v2 hiếm khi xong sớm hơn. Hai mươi vòng cách nhau 5 giây giữ tổng thời gian dưới trần 120 giây của query JavaScript.
Bước 5: dựng giao diện cho app
Sáu component là đủ cho một app một nút bấm:
| Component | Cấu hình |
|---|---|
Text Input sitekeyInput |
Nhãn "reCAPTCHA Sitekey" |
Text Input pageurlInput |
Nhãn "URL trang" |
Button solveButton |
Nhãn "Giải CAPTCHA", onClick solveCaptcha.trigger() |
| Text trạng thái | {{ solveCaptcha.isFetching ? "Solving..." : "" }} |
Text Area tokenOutput |
{{ solveCaptcha.data?.token }}, chế độ chỉ đọc |
| Status Badge | Hiện theo {{ solveCaptcha.data?.status }} |
Bước 6: dùng token trong query gửi form
Chuyển token sang query Retool gửi biểu mẫu. Tạo query tên submitForm:
| Cài đặt | Giá trị |
|---|---|
| Resource | API đích của bạn |
| Action Type | POST |
| Body | Dữ liệu form kèm g-recaptcha-response: {{solveCaptcha.data.token}} |
Chỉ bật nút "Gửi biểu mẫu" khi {{ solveCaptcha.data?.status === 'solved' }}. Ràng buộc nhỏ đó chặn phần lớn lỗi gửi form khi token còn rỗng.
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
ERROR_WRONG_USER_KEY |
Key sai hoặc thiếu trong Secrets Store | Kiểm tra Settings → Secrets |
| Query trả về text thô, không phải JSON | Thiếu json=1 |
Thêm json: 1 vào query parameter |
| Polling hết thời gian chờ | Loại CAPTCHA cần giải lâu hơn | Tăng số vòng lặp từ 20 lên 30 |
submitCaptcha.data là undefined |
Query gửi chưa chạy | Chạy query gửi trước khi polling |
| Form vẫn báo CAPTCHA sai | Token đã hết hạn | Gửi form ngay khi status === 'solved' |
Kịch bản thực tế: nhóm vận hành ở TP.HCM
Một công ty outsourcing dùng app Retool để đối soát đơn hàng với cổng của đối tác. Cổng đó bật reCAPTCHA v2 ở form đăng nhập, nên mỗi ca nhân viên phải mở trình duyệt, giải tay rồi dán kết quả lại. Thêm resource CaptchaAI xong, app tự lấy token; phía người dùng chỉ còn một nút bấm.
Hai điểm nên chốt trước khi bàn giao:
- Chỉ trỏ luồng này vào hệ thống bạn có quyền truy cập hợp lệ: cổng đối tác đã ký hợp đồng, hệ thống nội bộ của đơn vị bạn, hoặc staging.
- Ghi log mỗi lần giải: thời điểm, người bấm nút, ID task, kết quả. Khi luồng chạm dữ liệu cá nhân, Nghị định 13/2023/NĐ-CP khiến phần log này thành thứ bạn cần lúc rà soát.
Chi phí: tính theo thread, không theo lượt giải
CaptchaAI bán theo thread — số CAPTCHA được giải song song — và mỗi thread không giới hạn lượt giải trong tháng. App nội bộ thường chỉ vài người bấm nút cùng lúc:
| Gói | Giá | Thread |
|---|---|---|
| BASIC | $15/tháng | 5 |
| STANDARD | $30/tháng | 15 |
| ADVANCE | $90/tháng | 50 |
Giá niêm yết bằng USD, thanh toán bằng thẻ quốc tế, không có bảng giá quy đổi VND. Với app một nút bấm như trên, BASIC ($15/tháng, 5 thread) thường đã đủ.
Các loại CAPTCHA khác dùng lại được khung này
Chỉ cần đổi method và bộ tham số trong query gửi:
| Loại CAPTCHA | Trạng thái |
|---|---|
| reCAPTCHA v2, v2 Invisible, v2 Enterprise, v3 | ✅ |
| Cloudflare Turnstile, Cloudflare Challenge | ✅ |
| GeeTest v3, CAPTCHA ảnh/OCR, grid-image, BLS | ✅ |
| CaptchaFox, Friendly Captcha, Lemin | ✅ beta |
| hCaptcha, FunCaptcha (Arkose Labs) | ❌ Chưa hỗ trợ |
| GeeTest v4 | ❌ Sắp ra mắt |
Ba loại beta mới mở, nên thử trong app nháp trước.
Câu hỏi thường gặp
Query JavaScript của Retool timeout ở 120 giây thì làm sao?
Giữ tổng thời gian dưới ngưỡng: 15 giây chờ đầu cộng 20 vòng cách nhau 5 giây là khoảng 115 giây. Nếu vẫn chạm trần, tách thành hai query: một query gửi task và lưu ID, một query polling bấm lại.
Cất API key CaptchaAI trong Retool thế nào cho an toàn?
Dùng Secrets Store (Environment → Secrets) rồi gọi qua {{secretsStore.CAPTCHAAI_API_KEY}}. Khóa nằm ngoài định nghĩa query nên người chỉ có quyền xem app không đọc được.
Một app Retool nội bộ cần bao nhiêu thread?
Bằng số lượt giải chạy đồng thời ở giờ cao điểm, không phải tổng số lượt trong ngày. Năm người cùng bấm nút trong một phút vẫn gọn trong 5 thread của gói BASIC ($15/tháng).
Chuyển luồng này sang Cloudflare Turnstile thì sửa gì?
Đổi method thành turnstile và thay googlekey bằng sitekey; phần polling giữ nguyên. Khác biệt còn lại ở query gửi form: token Turnstile đi trong trường cf-turnstile-response.
Bài viết liên quan
- Giải reCAPTCHA v2 dạng callback bằng API
- Xử lý reCAPTCHA v2 và Turnstile trên cùng một trang
- Cơ chế callback của reCAPTCHA v2
Bước tiếp theo
Dựng resource CaptchaAI trong workspace Retool và để app tự lấy token — lấy API key CaptchaAI rồi quay lại Bước 1.
Hướng dẫn liên quan: