How to Solve CAPTCHA in n8n with the HTTP Request Node

There is no n8n CAPTCHA node, and you do not need one. Two HTTP Request nodes and a Wait node solve any supported CAPTCHA type: the first submits the job, the second polls until the token comes back. Point both at 127.0.0.1:8080 and the whole loop runs on the same machine as n8n, so nothing leaves your network and nothing is metered per solve. CapSkip also runs on a server and takes the same API calls over the network, which is how you use it from n8n Cloud. This guide builds the workflow node by node, including the Docker networking trap that catches most people on the first run.
What you need
- n8n, self-hosted, desktop or cloud. All three work, though cloud needs CapSkip reachable over the network. See the server section below.
- The CapSkip app running with its service started. Port, key and Local or Server mode are in the connection settings.
- A sitekey and a page URL from the site you are automating.
Key validation is off by default, so any non-empty string works as the key parameter. Send something rather than nothing: an empty key returns ERROR_WRONG_USER_KEY.
Why HTTP Request instead of a community node
Community nodes have to be installed on the instance, they lag behind API changes, and on n8n Cloud they are restricted. The API here is 2captcha compatible and only has two endpoints, so a community node would wrap about six lines of configuration. The built-in HTTP Request node does the same job, upgrades itself with n8n, and works on every instance type.
| Endpoint | What it does | Returns |
|---|---|---|
/in.php | Submits a task | A numeric captcha ID |
/res.php | Asks whether that ID is done | The token, or the CAPCHA_NOT_READY string |
Step 1: submit the CAPTCHA
Add an HTTP Request node and name it Submit CAPTCHA. Set the method to POST and the URL to http://127.0.0.1:8080/in.php. Turn on Send Body, choose Form Urlencoded, and add these fields.
| Name | Value |
|---|---|
| key | Any non-empty string |
| method | userrecaptcha |
| googlekey | Your sitekey |
| pageurl | The page the widget sits on |
| json | 1 |
Always send the json field. Without it the response is a bare string like OK|2122988149 that you have to split by hand. With it you get an object n8n can address directly.
// Response from in.php with json=1
{"status": 1, "request": "2122988149"}
// The captcha ID is now available downstream as:
// {{ $json.request }}Other CAPTCHA types change only the method field and the parameters beside it. Turnstile uses turnstile with a sitekey and pageurl pair, GeeTest uses geetest with gt and challenge, and an image CAPTCHA uses base64 with the image bytes in a body field. The API reference lists every parameter per type.
Step 2: wait before the first poll
Add a Wait node after Submit CAPTCHA. Set Resume to After Time Interval, Wait Amount to 15, and Wait Unit to seconds.
Fifteen seconds is not arbitrary. A reCAPTCHA v2 job is rarely ready sooner, so polling immediately just burns a workflow execution on a guaranteed CAPCHA_NOT_READY. Turnstile and GeeTest clear faster, so 5 seconds is enough there. An image CAPTCHA is usually done in about a second.
Step 3: poll for the token
Add a second HTTP Request node named Poll Result. Method GET, URL http://127.0.0.1:8080/res.php, Send Query Parameters on.
| Name | Value |
|---|---|
| key | The same string you used in step 1 |
| action | get |
| id | {{ $('Submit CAPTCHA').item.json.request }} |
| json | 1 |
Reference the submit node by name rather than using $json.request. Once the Wait node sits between them, the incoming item belongs to the Wait node, and on the second pass around the loop it belongs to the IF node. Naming the source node explicitly keeps the ID stable no matter how many times the loop runs.
Step 4: loop until it is ready
Add an IF node after Poll Result. The condition is a string comparison: left value {{ $json.request }}, operation “is not equal to”, right value CAPCHA_NOT_READY.
Connect the false output back to the Wait node. That closes the loop, and n8n keeps cycling until the answer arrives. The true output carries the token onward.
// res.php while the job is still running
{"status": 0, "request": "CAPCHA_NOT_READY"}
// res.php once it is solved
{"status": 1, "request": "03AGdBq26..."}Note the spelling. The API returns CAPCHA_NOT_READY without the first T, inherited from the 2captcha wire format it is compatible with. Type it the way it looks correct and the IF node will never match, so the loop runs until the workflow times out.
Results are readable once only. Reading the same ID twice returns an error rather than the token again, so send the token straight into the next node instead of polling once to check and again to fetch.
Step 5: use the token
The true branch of the IF node now carries the token. Submit it as the g-recaptcha-response form field on whatever request you were blocked from making, using a third HTTP Request node.
// Code node, Mode: Run Once for All Items.
// Builds the form payload for the final request.
const token = $input.first().json.request;
return [
{
json: {
email: "[email protected]",
"g-recaptcha-response": token,
},
},
];Tokens expire about two minutes after they are issued, so do the submit immediately. If your workflow has an approval step or another Wait node between solving and submitting, move the solve to after it.
The Docker trap: localhost is not your machine
This is the single most common failure, and the error message points nowhere useful. Inside a container, 127.0.0.1 means the container itself, not the host running the solver. n8n reports a connection refused and the workflow dies at Submit CAPTCHA.
On Docker Desktop for Mac and Windows, swap the host for host.docker.internal in both URLs. On Linux that name does not resolve by default, so add it explicitly when you start the container.
# docker pull docker.n8n.io/n8nio/n8n # Linux: map host.docker.internal to the host gateway. docker run -it --rm \ --add-host=host.docker.internal:host-gateway \ -p 5678:5678 \ -v n8n_data:/home/node/.n8n \ docker.n8n.io/n8nio/n8n # Both node URLs then become: # http://host.docker.internal:8080/in.php # http://host.docker.internal:8080/res.php
If n8n and the solver run on the same Docker network, use the service name instead. The rule is the same either way: the URL has to resolve from inside the container, not from your terminal.
Running CapSkip on a server instead
CapSkip does not have to live on the same machine as n8n. The connection settings have two modes, and the second one is what makes this work with a hosted n8n.
| Mode | Listens on | Use it when |
|---|---|---|
| Local | 127.0.0.1, that device only | n8n and CapSkip run on the same computer |
| Server | Your network or public IP | n8n runs elsewhere: another box, a VPS, or n8n Cloud |
Put CapSkip on a VPS in Server mode and every machine on your team points at one instance. The node URLs become http://YOUR_SERVER_IP:8080/in.php and http://YOUR_SERVER_IP:8080/res.php, and nothing else in the workflow changes. A static public IP is worth having, because the URLs are pinned in the nodes and a changing address breaks them.
This is still your own hardware and still unmetered. Server mode moves where the solver runs, not who owns it, so you are not paying per solve either way.
Common errors and what they mean
| Response | Cause | Fix |
|---|---|---|
ECONNREFUSED | The solver is not running, or the container cannot see the host | Start the local service, then apply the Docker fix above |
ERROR_WRONG_USER_KEY | The key field is empty or missing | Send any non-empty string |
ERROR_GOOGLEKEY | The sitekey is wrong, truncated or from a different page | Re-read the data-sitekey attribute on the live page |
ERROR_PAGEURL | The pageurl field is missing a scheme or points somewhere else | Send the full URL including https |
| Loop never exits | The IF node compares against a misspelled ready string | Match the CAPCHA_NOT_READY spelling exactly |
ERROR_CAPTCHA_UNSOLVABLE | The job failed rather than timed out | Resubmit with a fresh challenge value |
Every code the API can return is listed in the API documentation, with the parameter that causes each one.
FAQ
Does this work on n8n Cloud?
Yes, with CapSkip in Server mode. Cloud workers run in n8n’s infrastructure, so they cannot see 127.0.0.1 on your desktop, and that is the only address Local mode allows. Switch the connection settings to Server mode, run CapSkip on a VPS with a static public IP, and point the nodes at that address. If you would rather keep everything on one box, self-host n8n alongside it and stay in Local mode.
How many executions does the polling loop cost?
One. A loop inside a workflow is still a single execution no matter how many times it cycles, so the Wait and IF pair does not multiply your execution count. It does hold the execution open, which matters if you run many workflows concurrently on a small instance.
Can I solve several CAPTCHAs in one run?
Yes. The HTTP Request node runs once per input item, so feeding it a list of sitekey and URL pairs submits them all and gives you one captcha ID per item. Keep the IDs paired with their source items through the loop, and use a Split In Batches node if you want to cap how many are in flight at once.
Do I need a proxy?
Usually not. Add the proxy and proxytype fields to the submit node only when the target site rejects tokens solved from a different network than the one that loaded the page. Proxies apply to reCAPTCHA, Turnstile and GeeTest, and are ignored for image CAPTCHAs.
Wiring it into a real workflow
The five nodes above drop into any workflow that hits a protected form: a lead-capture scraper, a nightly price check, an internal tool that logs into a legacy portal. Because the solver is a captcha solver running on your own hardware, the sitekeys and page URLs you feed it never leave the machine, and the loop costs nothing per run. If you would rather call it from a Code node in JavaScript or Python instead of HTTP Request nodes, the official SDKs wrap the same two endpoints, and reCAPTCHA v2 specifics are covered separately.
