How to Solve CaptchaFox in C# Without a Rejected Token

solve captchafox in c# - How to Solve CaptchaFox in C# Without a Rejected Token

To solve CaptchaFox in C#, call CaptchaFoxAsync with the sitekey and the page URL, put the returned token in the cf-captcha-response form field, and send that request with the user agent CapSkip returns alongside the token. The last part is where most integrations fail. A CaptchaFox token is bound to the browser that produced it, and a mismatched user agent is the most common reason a correct token gets turned down, even though the solve itself worked. CapSkip added CaptchaFox in version 1.4.0. This guide covers finding the key, the call, the submit, and the few settings that decide whether the token is accepted.

What you need

  • CapSkip 1.4.0 or later running on a Windows machine. CaptchaFox support arrived in that release, alongside Friendly Captcha and Capy Puzzle.
  • Version 1.3.0 or later of the CapSkip .NET package, the release that added CaptchaFoxAsync. It targets .NET Standard 2.0 and depends on System.Text.Json 8, so it is supported on .NET Framework 4.6.2 and later and on .NET 6 and later. The samples use top-level statements and the implicit usings of a .NET 6 console project, so run them on .NET 6 or later.
  • Two values from the target page: the sitekey and the URL of the page the widget runs on. Step 1 shows where the key lives, and one more detail from the page decides an optional setting.
  • An address for the solver. Local mode answers on 127.0.0.1 for that device only; Server mode listens on your network address or public IP so another box can call it over the API. Both live under connection settings, and a section below covers when to switch.
# dotnet add package CapSkip
dotnet add package CapSkip

Step 1: find the sitekey and the widget source

Everything you need to solve CaptchaFox in C# comes off the target page. The sitekey is public, identical for every visitor, and conventionally starts with sk_. Sites put it in one of three places, and you only need to find one of them.

  • On the container element, when the widget renders itself: a div with the class captchafox and a data-sitekey attribute. Hidden mode, which shows nothing until the form is submitted, uses the same div with data-mode set to hidden, so the key is there too.
  • In the options of a captchafox.render call, when the page builds the widget from its own script. A render call can switch on hidden mode as well, with the key sitting right beside it.
  • In the Network tab, when neither of those appears in the served HTML. Find the request to api.captchafox.com; the key is the path segment after /captcha/.

While the page source is open, look at the script tag that loads the widget. The widget reaches pages from two places, and the one a site loads decides what shape of token that site expects back.

The page loads the widget fromToken that comes backWhat to pass
https://cdn.captchafox.com/, the standard widget used by most sitesA plain tokenNothing extra; this is the default
A package under https://s.uicdn.com/mampkg/, which some platforms embedA token that starts with MAM_The full package path from the script tag, as the api_server option

Getting this wrong fails quietly. If you send the wrong source, the solve still succeeds and returns a token, just in a format the site will not accept, so the failure shows up later as a rejected form rather than as an error you can catch.

Step 2: the CaptchaFoxAsync call

The method takes the sitekey, the page URL, and an optional options dictionary. For the standard widget you need nothing more than the first two.

// dotnet add package CapSkip
using CapSkip;

var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);

// The sitekey from the widget, and the page the widget runs on.
var result = await solver.CaptchaFoxAsync(
    "YOUR_SITEKEY",
    "https://example.com/signup");

Console.WriteLine(result.Token);       // goes in cf-captcha-response
Console.WriteLine(result.UserAgent);   // send this as the User-Agent

Read the Token property. The Code property holds the same string, but Token is named for the field it goes into. UserAgent is the identity of the browser that minted the token, which is CapSkip’s own browser and not anything you sent. The SDK leaves it null when a solve reported none rather than inventing a value.

The page URL matters more here than for most types. CaptchaFox registers each key against a list of allowed domains and checks the host before it issues anything, so a key sent with the wrong page is refused permanently, not now and then. CapSkip reports that case straight away instead of retrying it, because a retry cannot help. Send the page the widget actually runs on, not a search result, a redirect or a shortened link.

Sites that load the MAM package

If Step 1 turned up a script under s.uicdn.com, copy its package path exactly as the page writes it and pass it as api_server. The token then comes back with the MAM_ prefix the site expects.

// Only for pages whose script tag loads the MAM build.
var result = await solver.CaptchaFoxAsync(
    "YOUR_SITEKEY",
    "https://example.com/signup",
    new Dictionary<string, object?>
    {
        ["api_server"] =
            "https://s.uicdn.com/mampkg/@mamdev/core.frontend.libs.captchafox/",
    });

The options dictionary takes api_server, proxy and useragent, along with proxytype and a per-call timeout in seconds. The useragent option exists for compatibility with other services and is not applied, because CapSkip solves in a real browser with that browser’s own consistent identity. An unrecognised key, or an empty sitekey or page URL, raises a ValidationException before a request is made.

Step 3: submit under the user agent that minted the token

This is the step that separates an accepted token from a rejected one. CaptchaFox scores the browser that runs the widget, and the token it issues belongs to that browser. HttpClient sends no User-Agent header at all unless you add one, and a hard-coded desktop string is just as wrong, so copy the one CapSkip returned onto the request that carries the token.

// http is your HttpClient; result comes from Step 2.
var request = new HttpRequestMessage(HttpMethod.Post, "https://example.com/signup")
{
    Content = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["email"] = "[email protected]",
        ["cf-captcha-response"] = result.Token!,
    }),
};

// The token is bound to the browser that produced it.
if (result.UserAgent is { } ua)
    request.Headers.TryAddWithoutValidation("User-Agent", ua);

var response = await http.SendAsync(request);

TryAddWithoutValidation is deliberate. The stricter UserAgent.ParseAdd checks the string against the header grammar and throws a FormatException when the two disagree, while this value has to go out byte for byte, whatever it contains. Set it per request rather than on the client’s default headers, so one HttpClient can carry tokens from several solves.

Two more rules come from how CaptchaFox checks the token on the site’s side. CaptchaFox’s own documentation says each token can be verified only once, and only within a short time, so solve when you are about to submit, send the token once, and solve again if the form is abandoned and resumed. And treat the token as opaque: it is checked against the session that produced it, so trimming or re-encoding it breaks it. Some integrations send it in a JSON body rather than a form post, so submit the form once by hand with DevTools open and mirror exactly what the page sends.

Step 4: proxies, challenge types and timeouts

CaptchaFox scores the network a widget runs on as well as the browser. One address is fine for testing and occasional solves, but repeated solves from it push that address towards interactive challenges and then towards refusals. Once you solve at any volume, configure a proxy pool in CapSkip, or send a per-request proxy. If you do, submit through the same exit as well, so the token and the form arrive from one network. CaptchaFox’s verification lets a site pass the visitor’s IP address along with the token, which is one more reason to keep the two on one route.

// using System.Net; for WebProxy and NetworkCredential.
// Same exit address for the solve and the submit.
var result = await solver.CaptchaFoxAsync("YOUR_SITEKEY", pageUrl,
    new Dictionary<string, object?>
    {
        ["proxy"] = new Proxy("HTTP", "login:[email protected]:8080"),
    });

var http = new HttpClient(new HttpClientHandler
{
    Proxy = new WebProxy("http://1.2.3.4:8080")
    {
        Credentials = new NetworkCredential("login", "password"),
    },
});

You do not choose which challenge appears. Most solves draw nothing at all, because the browser evidence clears on its own, and CapSkip also completes the slider when CaptchaFox asks for one. The two rare fallbacks are not solved.

Challenge typeHow oftenSolved
Invisible, with nothing drawnUsuallyYes
Slide puzzleSometimesYes
Image selectRarelyNo, reported unsolvable
AudioRarelyNo, reported unsolvable

An unsolvable report arrives as an ApiException, quickly, instead of waiting out the timeout. A retry usually draws a different challenge, so treat it as a reason to resubmit rather than as a broken key. The full example below retries that case twice and nothing else, since an ApiException also covers errors a retry cannot fix, such as a wrong API key.

CaptchaFoxAsync polls on recaptchaTimeout, 300 seconds by default, because it is a real browser session and runs longer when a slider is drawn. CapSkip runs its own clock as well: a task can wait up to 250 seconds (Wait Timeout) for one of the 10 CaptchaFox threads (Max. Threads), and a single attempt gets 150 seconds (Row Timeout). An attempt slowed down by a proxy therefore fails inside CapSkip first, and a longer SDK timeout does not give it more time. With Retries at its default of 0 and a thread free, that failure reaches you as an ApiException well before the SDK’s 300 seconds are up; if the task queues for a long time, the SDK can reach its limit first and throw a CapSkip.TimeoutException instead.

The SDK timeout only needs to grow when you raise Retries (0-3) in CapSkip’s CaptchaFox settings, because each extra retry can add up to one more Row Timeout to a single call. Then pass a longer timeout in the options dictionary, or raise recaptchaTimeout in the constructor.

Running the solver somewhere else

The samples use 127.0.0.1 because that is right while your code and CapSkip share a machine. Once the .NET app runs anywhere else, such as a container, a build agent, a VPS or an app service, loopback points at the wrong box and the first solve throws a NetworkException. Switch CapSkip to Server mode and it listens on your network address or public IP, so any of those can reach it over the API. Use a static public IP if the route crosses the internet, turn on API key validation, and restrict the port to the addresses you expect with a Windows Firewall rule. It is still your own Windows machine, and solving is still unmetered.

The client does not read environment variables by itself. Read CAPSKIP_HOST and CAPSKIP_API_KEY in your own code and pass them to the constructor, as the full example does, so the same build runs on your desk and on a server.

Full working example

// dotnet add package CapSkip
using System.Text.RegularExpressions;
using CapSkip;

// Copied by hand from the script tag, for pages that load the MAM build.
const string MamPackage = "https://s.uicdn.com/mampkg/@mamdev/core.frontend.libs.captchafox/";

var pageUrl = "https://example.com/signup";
var http = new HttpClient();
var solver = new CapSkipClient(
    apiKey: Environment.GetEnvironmentVariable("CAPSKIP_API_KEY") ?? "capskip",
    host: Environment.GetEnvironmentVariable("CAPSKIP_HOST") ?? "127.0.0.1",
    port: 8080);

// Read the sitekey off the captchafox container, in either attribute order.
var html = await http.GetStringAsync(pageUrl);
var widget = Regex.Match(html,
    "<[^>]*class=\"(?:[^\"]*\\s)?captchafox(?:\\s[^\"]*)?\"[^>]*>").Value;
var sitekey = Regex.Match(widget, "data-sitekey=\"([^\"]+)\"").Groups[1].Value;
// MAM pages may carry the key in the script src instead.
if (sitekey.Length == 0)
    sitekey = Regex.Match(html,
        "captchafox[^\"]*/api\\.js\\?key=([^\"&]+)").Groups[1].Value;
if (sitekey.Length == 0)
    throw new InvalidOperationException("No sitekey in the HTML; find it in DevTools.");

// The same CDN serves other packages, so match the captchafox one.
var options = new Dictionary<string, object?>();
if (html.Contains("mampkg/@mamdev/core.frontend.libs.captchafox"))
    options["api_server"] = MamPackage;

try
{
    // Image-select and audio come back unsolvable; a retry redraws.
    // Other API errors, such as a wrong key, are not worth repeating.
    SolveResult? result = null;
    for (var attempt = 1; result is null; attempt++)
    {
        try
        {
            result = await solver.CaptchaFoxAsync(sitekey, pageUrl, options);
        }
        catch (ApiException ex) when (attempt < 3 && ex.Message.Contains("UNSOLVABLE"))
        {
            Console.WriteLine($"attempt {attempt}: {ex.Message}");
        }
    }

    var request = new HttpRequestMessage(HttpMethod.Post, pageUrl)
    {
        Content = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["email"] = "[email protected]",
            ["cf-captcha-response"] = result.Token!,
        }),
    };
    if (result.UserAgent is { } ua)
        request.Headers.TryAddWithoutValidation("User-Agent", ua);

    // Post wherever the form's action attribute points.
    var response = await http.SendAsync(request);
    Console.WriteLine($"{(int)response.StatusCode}, UA sent: {result.UserAgent is not null}");
}
catch (CapSkip.ValidationException ex)
{
    // An option the method does not take.
    Console.WriteLine($"not sent: {ex.Message}");
}
catch (CapSkip.TimeoutException)
{
    Console.WriteLine("gave up waiting; recaptchaTimeout is 300 seconds");
}
catch (CapSkipError ex)
{
    // The third unsolvable result, a refused key, or CapSkip unreachable.
    Console.WriteLine($"gave up: {ex.Message}");
}

The regex bounds the class name by whitespace, so an element such as captchafox-wrapper does not match by accident, and it expects double-quoted attributes, as CaptchaFox’s own snippet writes them. If the solve fails instantly on every run, check the page URL before anything else: a key used outside its registered domains fails the same way every time. When the regex finds nothing, the page renders the widget from script, and the key is in the render call or the Network tab as described in Step 1. The raw endpoint behind this method is documented in the API reference, and every other method the package exposes is listed on the C# CAPTCHA solver page.

Common errors and what they mean

What you seeCauseFix
The site rejects a token that CapSkip returned as solvedThe request went out under a different User-Agent from the one the token was minted withSend result.UserAgent on the submit request, byte for byte
Rejected although the user agent matchesThe site loads the MAM package and the solve used the default widget, or the other way roundRead the script tag and set api_server to match it
A token beginning with MAM_ that the site refusesapi_server was set for a page that loads the standard widgetDrop the option and use the default
An ApiException straight away, every time, for one keyThe page URL is outside the domains the key is registered forSend the page the widget runs on, not a redirect or a search result
An occasional ApiException saying the CAPTCHA is unsolvableCaptchaFox drew an image-select or audio challengeResubmit; the next attempt usually draws a different one
More challenges, then refusals, as a run goes onEvery solve comes from one address and CaptchaFox scores the networkSpread solves across a proxy pool, and submit through the same exit
A token that worked once fails on the second submitEach token verifies once and expires quicklySolve fresh for every submit, and submit straight away
A ValidationException before anything is sentThe sitekey or page URL was empty, or the options held a key the method does not takeCheck the regex found the element, and drop the option the message names
A NetworkException on the first solveCapSkip is not running, or the host and port are wrongStart CapSkip, then check whether it should be in Local mode or Server mode
The build fails on an ambiguous TimeoutExceptionCapSkip and System both define that short nameWrite CapSkip.TimeoutException in full, or catch CapSkipError

FAQ

Why does CaptchaFox need the user agent when most CAPTCHA types do not?

Because it scores the browser rather than asking a person to read anything. The token is the service’s verdict on one particular browser, so it only makes sense coming back from that browser. Cloudflare Turnstile challenge pages work the same way, and the C# side of that is covered in the Turnstile challenge page guide. CapSkip returns the user agent for exactly those two types, which is a reliable hint about where it matters.

Can a .NET app on a hosted platform reach the solver?

Yes. Switch CapSkip to Server mode under connection settings so it listens on a network address instead of loopback, read that address from CAPSKIP_HOST in your code, and pass it to the client. A container host, a VPS, a CI agent and a managed app service all connect the same way, over the same HTTP API. Use a static public IP with a firewall rule if the route crosses the internet. The solver stays on hardware you own, so nothing about the licence or the solve count changes.

How is this different from solving Friendly Captcha in C#?

Both arrived in CapSkip 1.4.0 and both use the 300 second timeout, and that is most of the overlap. With Friendly Captcha the decision is which protocol version the site runs, and the token is not tied to a user agent. With CaptchaFox the decisions are which widget source the site loads and which user agent carries the token. The Friendly Captcha side is covered in the C# Friendly Captcha guide.

Can I solve several CaptchaFox tokens at once?

Yes. The client is async, so a Task.WhenAll over several CaptchaFoxAsync calls runs them side by side. CapSkip runs 10 CaptchaFox solves at a time by default (Max. Threads in its CaptchaFox settings), and the rest wait for a free thread for up to the 250 second Wait Timeout, so a batch of fifty is fine at normal solve times; for bigger batches, raise Max. Threads or send the calls in smaller groups. Two more things to plan for: each token pairs with its own user agent, so keep the result together with the request it belongs to, and concurrency from one address raises the challenge rate sooner, so add proxies as you add parallel solves.

The short version

To solve CaptchaFox in C#, read the sitekey from the captchafox container, the render call or the Network tab, and check which script loads the widget. Call CaptchaFoxAsync with the sitekey and the real page URL, adding api_server only for the MAM package. Post result.Token in cf-captcha-response under result.UserAgent, once and straight away, and resubmit if an unsolvable challenge comes back. Add proxies as volume grows, and switch to Server mode when the calling code leaves the solver’s machine.

One last thing that shapes how you retry. When an image-select challenge sends a solve back, the fix is simply another attempt, and with an unlimited captcha solver running on your own machine that retry costs a few seconds rather than another billed solve.