How to Solve CAPTCHAs in Activepieces (HTTP Piece)

An Activepieces captcha solve is three steps: submit the challenge, wait, read the token. Build it on the HTTP piece rather than on a Code step, because whether a Code step can use npm at all depends on which sandbox mode your instance runs, and the mode Activepieces Cloud uses does not have npm. The HTTP piece works in every mode. There is a second setting that matters more than the code does, and it decides whether your flow can reach a solver on a private address at all.
What you need
- An Activepieces project you can publish a flow in, on their cloud or self-hosted.
- CapSkip running on a Windows machine. Local mode is fine only if Activepieces runs on that same machine, which in practice means a self-hosted install. Everything else needs Server mode.
- The sitekey and the page URL of the site you are automating.
- A project variable holding the solver key, so it does not sit in the flow body.
- On a self-hosted instance with a hardened network, one entry in the SSRF allow list. Step 3 covers this.
Why the HTTP piece and not a Code step
The Code step editor has an Add npm package dialog. It looks the package up in the npm registry, pins the latest version and writes it into the step’s dependency list. On Activepieces Cloud that list is then thrown away.
Activepieces builds a Code step by writing your source to a TypeScript file, installing its dependencies and bundling the result. The build only asks for your dependencies if the instance’s execution mode allows packages. In V8 sandboxing, which is the mode Activepieces documents as the one their cloud runs, packages are not allowed, so the build substitutes an empty dependency set and compiles anyway. The step deploys cleanly. The import fails when the flow runs.
| Execution mode | npm in a Code step | What it means for this flow |
|---|---|---|
| V8 sandboxing, the value SANDBOX_CODE_ONLY | No npm packages | Use the HTTP piece. This is Activepieces Cloud |
| Combined sandboxing, SANDBOX_CODE_AND_PROCESS | No npm packages | Use the HTTP piece |
| Kernel namespaces, SANDBOX_PROCESS | npm packages work | The Node SDK works, and it polls for you |
| No sandboxing, UNSANDBOXED | npm packages work | The Node SDK works, and it polls for you |
So there are two honest ways to do this, and which one you get is not your choice, it is your administrator’s. The HTTP piece route below works in all four. The Code step route at the end of this guide works in two of them and is much shorter when you have it.
Step 1: submit the challenge
CapSkip speaks the 2captcha-compatible API on port 8080, so the HTTP piece talks to it with no connector to install. Add a Send HTTP Request action, set the method to POST and the URL to the submit endpoint on your solver.
{
"key": "{{variables['CAPSKIP_KEY']}}",
"method": "userrecaptcha",
"googlekey": "YOUR_SITEKEY",
"pageurl": "https://example.com/page-with-recaptcha",
"json": 1
}The response is a small JSON object whose request field holds the id you poll with.
{ "status": 1, "request": "2122988149" }That 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 method to turnstile or geetest. The full parameter list is in the CapSkip API documentation.
Reference the key with the project variable syntax rather than pasting it. Variables are encrypted at rest and never shown back to you in the variables list, and rotating one is a single edit instead of a hunt through every flow.
Step 2: wait, then read the token
Add a Delay For action, then a second HTTP request that reads the result. Twenty 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.
# GET, with the id from step 1 in the query string. http://127.0.0.1:8080/res.php?key=YOUR_KEY&action=get&id=2122988149&json=1
Two answers are possible. A ready result is a JSON object shaped like the submit response, with the token in the request field. A result that is not ready yet is the string CAPCHA_NOT_READY, spelled without the T, and it 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.
The Delay piece behaves differently either side of ten seconds, and this is the detail that makes the polling shape cheap here. A delay of ten seconds or less sleeps in the worker process. Anything longer creates a waitpoint, suspends the run and resumes it when the clock expires. Suspended time is not execution time, and Activepieces documents that flows paused by Delay or by Wait for Approval do not count against the run timeout. So a twenty second wait costs you nothing against your ten minute budget, and neither does a second one.
If one read is not enough, add another Delay and another read rather than reaching for a loop. Two reasons. A Loop on Items runs every item in the list, so the iterations happen whether or not the token has already arrived, and a Router inside it saves you the work in the branch rather than the trip round the loop. More importantly, a CapSkip result is readable once, so a loop that re-reads an id it has already collected does not get the token twice, it gets an error on the second read.
Step 3: the network setting that blocks a local solver
This is the part that catches people, and it is specific to Activepieces rather than general orchestrator advice. Activepieces has an SSRF guard for flow code, controlled by a variable named AP_NETWORK_MODE. It defaults to UNRESTRICTED. Set to STRICT, the engine patches Node’s DNS lookup and socket connect before any flow code runs, and refuses any connection whose address is loopback, RFC1918 private, link-local or cloud metadata. It throws an error named SSRFBlockedError.
A CAPTCHA solver on your own network is exactly the shape that guard blocks. Both 127.0.0.1 and a LAN address like 192.168.1.40 are on the list. That is the guard doing its job, not a bug, and Activepieces gives you the documented exception for it: put the solver’s address in AP_SSRF_ALLOW_LIST, which takes comma separated IPs and CIDR ranges and applies to flow code and to the server’s own outbound requests alike. Restart the server after changing it.
# On a self-hosted Activepieces with AP_NETWORK_MODE=STRICT, # name the solver machine or its subnet so flows can reach it. AP_SSRF_ALLOW_LIST=192.168.1.40,10.0.5.0/24
Separately from the guard, the flow has to be able to reach the machine 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 Activepieces runs | Which mode, and what else |
|---|---|
| Self-hosted on the same Windows machine as CapSkip | Local mode, host stays 127.0.0.1. Allow-list it if the network mode is STRICT |
| Self-hosted in Docker or on another box on your network | Server mode with the solver’s LAN address. Allow-list that address too |
| Activepieces Cloud | Server mode with a static public IP and a firewall rule. The SSRF guard still runs but does not block, because a public address is not on its blocklist |
Step 4: timeouts and retries
Three numbers decide whether a slow solve survives, and only one of them is yours to set in the flow.
| Which limit | Value | Why it matters to a solve |
|---|---|---|
| The whole run, and any single action, are capped independently | Ten minutes each, from the one variable AP_FLOW_TIMEOUT_SECONDS | Comfortable, because delay time is not counted against the flow run timeout |
| Synchronous webhook response timeout | Thirty seconds, set by AP_WEBHOOK_TIMEOUT_SECONDS | The trap. See the paragraph below |
| Retry on Failure, per step | Four attempts, with waits of four, eight and sixteen seconds | Covers a solver that is restarting, not one that is merely slow |
The webhook number is the one that bites. A webhook URL ending in the word sync holds the HTTP connection open and answers with the flow’s result, and it gives up after thirty seconds. A reCAPTCHA v2 solve does not reliably finish inside thirty seconds, so a caller that triggers the flow synchronously and expects a token back gets HTTP 408 while the flow carries on running behind it. Trigger the flow asynchronously and have it post the token where you need it, or split the work so the synchronous half never waits on a solve.
Retry on Failure is worth turning on for the submit step and not for the read step. Its backoff is exponential from a two second base, so the waits are roughly four, eight and sixteen seconds across four attempts. That is right for a connection that was refused. It is wrong for a token you have already collected, because of the read-once rule above.
The whole thing in one step, on a self-hosted instance
If your administrator runs no sandboxing or kernel namespace sandboxing, the flow above collapses into one Code step, because the SDK polls for you. Add capskip in the npm dialog, then write the step. Code steps are TypeScript, bundled before they run, so an ordinary import works.
// npm install capskip - add it in the step's package dialog.
import { CapSkip } from 'capskip';
export const code = async (inputs) => {
// host is the solver machine. Keep 127.0.0.1 only when
// Activepieces runs on the same Windows box as CapSkip.
const solver = new CapSkip({
host: inputs.capskipHost,
port: 8080,
apiKey: inputs.capskipKey,
});
const result = await solver.recaptcha(inputs.sitekey, inputs.pageUrl);
// Return the token, not the whole result. The next step
// submits it, and run logs keep whatever you return.
return { token: result.code };
};Pass capskipKey in as a step input holding the project variable reference, so the key resolves at run time and never appears in the source. The SDK starts polling at 250 milliseconds and backs off, rather than sleeping a flat interval, which is why this version usually returns sooner than the Delay based flow does. Its ceiling for reCAPTCHA, Turnstile and GeeTest is 300 seconds, well inside the ten minute action timeout.
Submit the token in the step that follows immediately. A reCAPTCHA token is good for about two minutes, so a flow that solves, waits on an approval step and then submits will fail on a token that was perfectly 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 see | Cause | Fix |
|---|---|---|
| SSRFBlockedError in the run log | The network mode is STRICT and the solver is on a private address | Add the address to AP_SSRF_ALLOW_LIST and restart the server |
| The capskip module is not found when the flow runs | The sandbox mode discarded the dependency at build time | Rebuild the step as HTTP piece calls, or self-host in a mode that allows packages |
| Connection refused on port 8080 | CapSkip is bound to loopback and the worker is elsewhere | Switch to Server mode and use the solver’s network address |
| The read returns CAPCHA_NOT_READY every time | The delay is shorter than the solve takes | Raise the first Delay, or add a second delay and read |
| The second read of the same id fails | A CapSkip result is readable once | Store the token in a step output, never re-read the id |
| ERROR_WRONG_USER_KEY in the response | The project variable resolved to an empty string | Check the variable name, including its exact case |
| HTTP 408 from a synchronous webhook | The solve outlived the thirty second webhook timeout | Trigger asynchronously, or move the solve off the synchronous path |
| A valid token is rejected by the target site | It expired between the solve step and the submit step | Submit in the next step, with no approval or delay between them |
FAQ
Can I use CapSkip from Activepieces Cloud?
Yes, with Server mode. The workers are on Activepieces infrastructure rather than yours, so the solver has to listen on an address they can reach: a public IP, ideally a static one, with a firewall rule that admits their traffic. Nothing about the solver changes, only where it listens. What you cannot do on their cloud is use the Node SDK in a Code step, because that mode has no npm, so build the flow on the HTTP piece.
Why did adding the npm package appear to work?
Because the dialog is a UI feature and the filtering happens on the server. The dialog resolves the package against the npm registry and records it. At build time the server asks whether the execution mode permits packages, and if it does not, it swaps in an empty dependency set before installing. The step compiles and deploys with no warning. You find out at run time, when the import resolves to nothing.
Should the flow loop until the token arrives?
Usually not. A Loop on Items runs its full item list, so you pay for every iteration you configured, and each iteration would re-read an id that can only be read once. A delay of the right length plus a single read is both cheaper and correct, and a second delay and read is a fine fallback. Long delays are unusually cheap here, because a delay over ten seconds suspends the run rather than occupying a worker, and suspended time does not count against the run timeout.
How does this compare with n8n, Make.com or Zapier?
The requests are identical in all four tools. What differs is the obstacle each one puts in front of them.
- For n8n the obstacle is container networking, and the n8n workflow guide works through it.
- For Make.com it is the certificate its HTTP module demands, which the Make.com walkthrough covers in full.
- For Zapier it is the runtime ceiling on a Code step, explained in the Zapier guide.
Activepieces adds two obstacles of its own: a sandbox mode that decides whether npm exists at all, and an SSRF guard that can refuse a private address outright.
The short version
Build the solve on the HTTP piece, because it works in every sandbox mode and the Code step route does not. Submit to the submit endpoint, delay past ten seconds so the run suspends rather than occupying a worker, then read the result once and keep it. Put the key in a project variable. If the instance is self-hosted with a strict network mode, add the solver to the SSRF allow list, and if Activepieces runs anywhere other than the solver’s own machine, switch CapSkip to Server mode. Never wait for a solve on a synchronous webhook.
- The reCAPTCHA v2 checkbox itself is covered on the reCAPTCHA v2 solver page.
- The equivalent one-call versions in Python, Node.js, PHP and C# are listed on the CAPTCHA solving SDK page.
One thing worth weighing before you schedule this flow every few minutes: CapSkip is an unlimited captcha solver running on hardware you already own, so a flow that fires constantly and one that fires occasionally cost exactly the same.
