How to Solve CAPTCHAs in Retool Workflows (REST Blocks)

retool workflows captcha - How to Solve CAPTCHAs in Retool Workflows (REST Blocks)

A Retool Workflows captcha solve is three blocks: submit, wait, read. Build them as REST resource query blocks rather than as a JavaScript or Python code block, because Retool runs code blocks in a separate sandboxed service whose default firewall rules refuse private addresses. A resource query is configuration rather than custom code, so it does not go through that service, and it reaches a solver on your own network without any of that argument.

What you need

  • A Retool organization with Workflows, on Retool Cloud or self-hosted. Both work, with different network setup.
  • CapSkip running on a Windows machine. Local mode if that machine also runs a self-hosted Retool, Server mode in every other case.
  • The sitekey and the page URL of the challenge you are solving.
  • Somewhere to put the API key. Retool secrets are reachable from code blocks and from resource configuration, so nothing needs to be pasted into a block.

CapSkip speaks the 2captcha-compatible API on port 8080, so Retool needs no connector and no custom integration. It is an ordinary REST resource pointed at a machine you own.

Step 1: point a REST resource at the solver

Create a REST API resource with the base URL of the machine running CapSkip. Leave authentication empty. The API key travels as an ordinary parameter on each request, which is how the 2captcha-compatible protocol works.

# Base URL for the resource. Loopback only works when Retool
# is self-hosted on the same Windows box as the solver.
http://127.0.0.1:8080

# Server mode, which is what you want everywhere else.
http://192.168.1.40:8080

Every workflow that solves anything then reuses this one resource. Two query blocks are enough for the whole job.

Step 2: submit the challenge

Add a resource query block, name it submitCaptcha, set the action type to POST and the path to the submit endpoint. The body is a small JSON object.

{
  "key": "YOUR_KEY",
  "method": "userrecaptcha",
  "googlekey": "YOUR_SITEKEY",
  "pageurl": "https://example.com/page-with-recaptcha",
  "json": 1
}

The response carries the id you poll with.

{ "status": 1, "request": "2122988149" }

That body is reCAPTCHA v2. The other types CapSkip supports are the same call with different parameters: add invisible or enterprise set to 1, or version set to v3 with an action name, or switch the method to turnstile or geetest. The full parameter list is in the CapSkip API documentation.

A resource query block hands three properties to everything downstream. The response body arrives as data, a failure message arrives as error, and metadata carries the rest. So the id you just collected is available to the next block as submitCaptcha.data.request.

Step 3: wait, then read the token once

Add a Wait block. Fifteen seconds is a sensible first wait for a reCAPTCHA v2 checkbox. Image CAPTCHAs come back in about a second, v3 in ten to fifteen, GeeTest in about five. A Wait block takes a number or a JavaScript expression and can be configured in seconds, minutes, hours or days, up to a ceiling of sixty days, and it pauses only the blocks directly downstream of it.

Then add a second resource query block called readResult, set to GET.

# GET, with the id from step 2 in the query string.
/res.php?key=YOUR_KEY&action=get&id={{ submitCaptcha.data.request }}&json=1

Two answers are possible. A ready result has status set to 1 and the token in the request field. A result that is still working has status set to 0 and the string CAPCHA_NOT_READY in the request field, spelled without the T, and that means keep waiting rather than something went wrong. The history of that spelling is in the full write-up of the CAPCHA_NOT_READY response.

Handle the two cases with a Branch block. Conditions are plain JavaScript against an upstream block, so the test reads readResult.data.status === 1. The If path carries the token onward. The Else path gets a second Wait of twenty seconds and a second read.

Resist the urge to replace that with a Loop block. Two reasons, and the second is the one that actually bites. A Loop block has a default timeout of ten seconds and a ceiling of two minutes, which is well under the three hundred seconds CapSkip allows a reCAPTCHA solve, so a loop cannot cover the slow tail anyway. More importantly, a CapSkip result is readable once. A loop that re-reads an id it has already collected does not get the token a second time.

Step 4: the network rule that decides everything

This is the Retool-specific part, and it is why this guide builds the solve out of resource query blocks.

Retool runs your JavaScript and Python in a separate code executor service, sandboxed with NsJail. On a self-hosted deployment that service ships with iptables rules covering link local addresses and the whole of 192.168.0.0/16, and Retool documents a single switch that turns them off, DISABLE_IPTABLES_SECURITY_CONFIGURATION. Retool also states plainly that it recommends running the code executor privileged so custom code stays sandboxed. So a code block reaching for a solver at 192.168.1.40 is asking you to weaken the sandbox for the whole instance. A resource query is not custom code and does not run there.

Separately from that, the solver has to be reachable at all. CapSkip has two connection modes for this. Local binds to 127.0.0.1 and serves that device only. Server binds to your network address or public IP, so another box, a container host or a hosted platform can reach the same Windows machine over the API. Both live under connection settings, and Server mode changes only which address the solver listens on. It is still your hardware and it is still unmetered.

Where Retool runsWhich mode, and what else
Self-hosted on the same Windows machine as CapSkipLocal mode. The base URL stays on loopback
Self-hosted on another box or in Docker on your networkServer mode with the solver’s LAN address. Use a resource query block, not a code block
Retool CloudServer mode with a static public IP, plus an inbound firewall rule for Retool’s outbound addresses

Retool Cloud calls your resources from a fixed, published set of addresses, and the documentation says cloud instances must ensure configured resources allow access from them. The default region is AWS us-west-2.

# Retool Cloud outbound ranges, us-west-2, the default region.
35.90.103.132/30
44.208.168.68/30

# eu-central-1
3.77.79.248/30

Allow those on the firewall in front of port 8080 and refuse everything else. That is a much smaller opening than it looks, and it is the whole of the Retool Cloud story.

Step 5: the timeouts that decide whether a slow solve survives

Retool publishes different ceilings for different block types, and they matter here because a solve is slow by nature.

Which limitValueWhy it matters to a solve
Resource query block, asynchronous runUp to 10 minutesComfortable. A single read returns in well under a second
Resource query block, synchronous runUp to 2 minutesStill fine for a read, because the waiting happens in a Wait block
Loop block10 seconds by default, 2 minutes at mostThe reason a poll loop is the wrong shape here
Whole run, asynchronous30 hours, and unlimited with Wait blocksNothing about a solve comes close
Whole run, synchronous15 minutes up to the first webhook Response blockThe trap. See the paragraph below
Concurrent external requests per workflow50 at a timeThe real ceiling on a batch of solves in one run
Schedule trigger intervalOne minute minimumFine, and it costs nothing to run it that often

The synchronous number is the one to plan around. A webhook trigger that holds the connection open and answers with a Response block gives you fifteen minutes, which sounds generous until you remember that a caller sitting on an open HTTP connection for two minutes waiting on a reCAPTCHA is a bad design in its own right. Trigger the workflow asynchronously and have it post the token where it is needed, or answer the webhook immediately and do the solve behind it.

Resource query blocks also carry their own retry count and exponential backoff settings. Turn those on for submitCaptcha and leave them off for readResult, for the read-once reason above.

If you would rather write code

On a self-hosted instance where the code executor can reach the solver, the whole flow collapses into one Python block, because the SDK polls for you. Add capskip to the workflow’s requirements.txt in the Libraries tab first.

# pip install capskip - add it in the Libraries tab instead.
from capskip import CapSkip

# host is the solver machine. Keep 127.0.0.1 only when Retool
# runs on the same Windows box as CapSkip.
solver = CapSkip(host="192.168.1.40", port=8080)

result = solver.recaptcha(
    sitekey="YOUR_SITEKEY",
    url="https://example.com/page-with-recaptcha",
)

# Python blocks serialize their output as JSON, so return
# the token rather than the client object.
{"token": result["code"]}

The SDK starts polling at 250 milliseconds and backs off to five seconds, rather than sleeping a flat interval, so this version usually returns sooner than the Wait based flow. Its ceiling for reCAPTCHA, Turnstile and GeeTest is three hundred seconds, inside the ten minute asynchronous code block timeout. Retool Cloud runs Python 3.10 by default, and the equivalent one-call versions for Node.js, PHP and C# are on the CAPTCHA solving SDK page.

Submit the token in the block that follows immediately. A reCAPTCHA token is good for about two minutes, so a workflow that solves, waits on a long Wait block and then submits will fail on a token that was valid when it was made. That failure mode is covered in the guide to reCAPTCHA token expiration.

Common errors and what they mean

What you seeCauseFix
A code block times out reaching a LAN addressThe self-hosted code executor’s default iptables rules cover 192.168.0.0/16Move the call into a REST resource query block
Connection refused on port 8080CapSkip is bound to loopback and Retool is somewhere elseSwitch to Server mode and use the solver’s network address
Retool Cloud cannot reach the resource at allThe firewall does not admit Retool’s outbound addressesAllow the published ranges for your region on port 8080
readResult returns CAPCHA_NOT_READY every timeThe Wait block is shorter than the solve takesRaise the first wait, or add a second wait and read on the Else path
The second read of the same id comes back emptyA CapSkip result is readable onceKeep the token in the block output, never re-read the id
ERROR_WRONG_USER_KEY in the responseThe key parameter resolved to an empty stringCheck the secret name, including its exact case
A valid token is rejected by the target siteIt expired between the solve and the submitSubmit in the next block, with no Wait between them
A batch of solves stalls partway throughOne workflow may have 50 external requests in flightBatch the Loop block, or split the work across runs

FAQ

Can I use CapSkip from Retool Cloud?

Yes, with Server mode. Retool Cloud calls your resources from its own infrastructure, so the solver has to listen on an address they can reach: a public IP, ideally a static one. Retool publishes the outbound ranges it calls from, so the firewall rule is narrow rather than open to the world. Nothing about the solver changes and nothing becomes metered. The only difference is the address it listens on.

Why not just use a JavaScript block with axios?

Because of where that code runs. Retool executes code blocks in a separate service sandboxed with NsJail, and on a self-hosted deployment that service installs firewall rules that cover private ranges. Turning them off is a documented switch, but it is an instance-wide decision taken to make one workflow easier, and Retool recommends against it. A resource query block reaches the same solver with no such argument. Use code blocks for the logic and resource query blocks for the network.

Should the workflow loop until the token arrives?

No. A Loop block caps out at two minutes, well short of the three hundred seconds a reCAPTCHA solve is allowed, and it runs every iteration you gave it rather than stopping early. On top of that, a result can be read once, so repeated reads of the same id are wasted trips. A Wait block of the right length plus a single read is correct and cheap, and a Branch with a second wait and read behind it covers the slow cases.

How does this compare with n8n or Pipedream?

The two requests are identical in all three tools. What differs is the obstacle each one puts in front of them.

  • For n8n the obstacle is container networking, worked through in the n8n workflow guide.
  • For Pipedream it is the step runtime and where secrets live, covered in the Pipedream guide.
  • For Retool it is the code executor’s own firewall, which is why the flow above never puts an HTTP call in a code block.

The short version

Point a REST resource at the solver, POST the challenge, Wait fifteen seconds, GET the result, and Branch on whether it is ready. Keep the HTTP in resource query blocks rather than code blocks, because the code executor’s default firewall rules cover private addresses and loosening them is an instance-wide decision. Switch CapSkip to Server mode whenever Retool is not on the solver’s own machine, and on Retool Cloud allow only the published outbound ranges through to port 8080. Never wait on a solve inside a synchronous webhook.

One thing worth weighing before you point a scheduled workflow at this every minute: CapSkip is a local captcha solver running on hardware you already own, so a workflow that fires every minute and one that fires twice a day cost exactly the same.