Retool no trae ningún componente que resuelva un CAPTCHA, y no hace falta: bastan dos consultas de API REST y una de JavaScript que las coordina. Envías el desafío a CaptchaAI desde in.php, consultas el resultado en res.php y el token queda en una variable que inyectas en el formulario. Esta guía recorre esa implementación con reCAPTCHA v2, paso a paso.
Por qué esto aparece justo en las herramientas internas
Casos típicos en la región: una gestoría en México que registra decenas de expedientes en un portal de trámites, o un equipo de soporte en España que consulta solicitudes en un portal de cita previa. El flujo entero vive en Retool; solo el reCAPTCHA v2 del último paso obliga a resolverlo a mano.
| Dentro de la app | Qué hace |
|---|---|
| Entrada | Recibe el sitekey y la URL de la página |
| Envío | Manda el desafío a CaptchaAI (in.php) |
| Sondeo | Consulta el resultado hasta que llega (res.php) |
| Salida | Expone el token para el envío del formulario |
Antes de automatizar contra un portal de terceros, revisa sus términos de servicio y la normativa de protección de datos aplicable (RGPD y LOPDGDD en España, LFPDPPP en México). Trabaja solo sobre flujos autorizados.
Paso 1: da de alta CaptchaAI como recurso de API REST
En Retool, ve a Resources → Create New → REST API:
| Campo | Valor |
|---|---|
| Nombre | CaptchaAI |
| Base URL | https://ocr.captchaai.com |
| Autenticación | Ninguna (la clave API viaja como parámetro de consulta) |
Guarda el recurso: lo referenciarás desde todas las consultas.
Paso 2: la consulta de envío (in.php)
Crea una consulta submitCaptcha:
| Ajuste | Valor |
|---|---|
| Resource | CaptchaAI |
| Action Type | GET |
| URL Path | /in.php |
Parámetros de consulta:
| Clave | Valor |
|---|---|
key |
{{secretsStore.CAPTCHAAI_API_KEY}} |
method |
userrecaptcha |
googlekey |
{{sitekeyInput.value}} |
pageurl |
{{pageurlInput.value}} |
json |
1 |
Guarda la clave en el Secrets Store de Retool (Settings → Secrets): así no queda escrita en la definición de la consulta, que cualquier editor puede abrir.
Con otro tipo solo cambia el method: turnstile para Cloudflare Turnstile, geetest para GeeTest v3, post para CAPTCHA de imagen.
Transformador (opcional):
// Parse the response
const data = {{ submitCaptcha.data }};
if (data.status === 1) {
return { taskId: data.request, status: 'submitted' };
}
return { error: data.request, status: 'failed' };
Paso 3: la consulta de sondeo (res.php)
Crea una consulta llamada pollResult:
| Ajuste | Valor |
|---|---|
| Resource | CaptchaAI |
| Action Type | GET |
| URL Path | /res.php |
Parámetros de consulta:
| Clave | Valor |
|---|---|
key |
{{secretsStore.CAPTCHAAI_API_KEY}} |
action |
get |
id |
{{submitCaptcha.data.request}} |
json |
1 |
El transformador reduce las tres respuestas posibles a un estado que la interfaz entiende: resuelto, pendiente o error.
Transformador:
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' };
Paso 4: el ciclo de sondeo en una consulta de JavaScript
Las consultas anteriores no se llaman solas. Crea una consulta de JavaScript solveCaptcha que envíe la tarea y sondee hasta obtener el token:
// 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();
El techo de 20 iteraciones no es arbitrario: las consultas de JavaScript de Retool se cortan a los 120 segundos y toda la espera cabe ahí.
Paso 5: la interfaz mínima
Con la lógica lista, bastan seis componentes:
| Componente | Nombre | Configuración |
|---|---|---|
| Text Input | sitekeyInput |
Etiqueta "reCAPTCHA Sitekey" |
| Text Input | pageurlInput |
Etiqueta "URL de la página" |
| Button | solveButton |
Al hacer clic → solveCaptcha.trigger() |
| Text | — | {{ solveCaptcha.isFetching ? "Solving..." : "" }} |
| Text Area | tokenOutput |
{{ solveCaptcha.data?.token \|\| '' }}, solo lectura |
| Badge | — | Éxito o error según {{ solveCaptcha.data?.status }} |
Mostrar el token ayuda mientras depuras; en la versión final puedes ocultarlo.
Paso 6: usa el token en la consulta siguiente
El token viaja con el formulario. Crea una consulta submitForm:
| Ajuste | Valor |
|---|---|
| Resource | Tu API de destino |
| Action Type | POST |
| Body | Datos del formulario, incluyendo g-recaptcha-response: {{solveCaptcha.data.token}} |
Conéctala a un botón "Enviar formulario" habilitado solo cuando {{ solveCaptcha.data?.status === 'solved' }}. El token dura poco: enviarlo minutos después suele terminar en rechazo, así que encadena ambos pasos en la misma interacción.
Errores frecuentes y cómo salir de ellos
| Problema | Causa | Solución |
|---|---|---|
ERROR_WRONG_USER_KEY |
Clave ausente o incorrecta en el Secrets Store | Verifícala en Settings → Secrets |
| Respuesta en texto plano y no JSON | Falta el parámetro json=1 |
Añade json: 1 a los parámetros |
| Se agota el tiempo de sondeo | Ese tipo necesita más tiempo de resolución | Sube las iteraciones de 20 a 30 |
submitCaptcha.data está undefined |
La consulta aún no se ha ejecutado | Ejecuta el envío antes del sondeo |
| Se corta la consulta de JavaScript | Límite de 120 segundos de Retool | Deja 20 iteraciones con intervalos de 5 segundos |
Qué plan necesita una app interna
CaptchaAI factura por thread concurrente, no por resolución: cada plan incluye resoluciones ilimitadas por thread.
| Plan | Precio | Threads | Cuándo encaja |
|---|---|---|---|
| BASIC | $15/mes | 5 | Una app interna de uso interactivo |
| STANDARD | $30/mes | 15 | Varias apps sobre el mismo recurso |
| ADVANCE | $90/mes | 50 | Envíos en lote |
La escala en USD sigue hasta VIP-3. Para quien factura en moneda volátil, lo que cuenta es un costo mensual fijo, no por resolución.
Preguntas frecuentes
¿Necesito un plan distinto si varias apps de Retool comparten el recurso?
No necesariamente. El límite es de threads concurrentes, no de apps: una sola clave sirve para todas si la suma de resoluciones simultáneas cabe en tus threads. Lo que ocupa un thread:
- Cada
solveCaptchaen vuelo, venga de la app que venga - Un sondeo abierto a la espera del token
- Nada más: al resolverse, la consulta libera el thread
¿Puedo lanzar esto desde un workflow de Retool en vez de un botón?
Sí. Funciona igual dentro de un Retool Workflow, y ahí el límite de 120 segundos aprieta menos: puedes separar envío y sondeo en pasos con sus propios reintentos.
¿Sirve tanto en Retool Cloud como en la versión autoalojada?
En ambas. Las dos admiten recursos de API REST y consultas de JavaScript, y una instancia autoalojada llama a la API directamente, sin proxy.
Siguiente paso
Monta el recurso, pega las consultas y prueba el ciclo con un sitekey de staging: obtén tu clave API de CaptchaAI y la app resolverá su primer reCAPTCHA v2 hoy mismo.
Para seguir leyendo
- resolver el callback de reCAPTCHA v2 con la API
- convivencia de reCAPTCHA v2 y Turnstile en un mismo sitio
- cómo funciona el mecanismo de callback de reCAPTCHA v2
- Zapier + CaptchaAI: resolver CAPTCHA sin escribir código
- Make (Integromat) + CaptchaAI
- extraer los parámetros de reCAPTCHA del código de la página