# From detection to integration

Take a challenge the resolvr extension detected straight into your code: export the task, solve it with one call, and submit the token, by hand or through a coding agent.

> resolvr blog — https://resolvr.com/blog/from-detection-to-integration · 2026-09-17 · resolvr team · Blog index: https://resolvr.com/blog

The resolvr extension has detected a challenge on the page you are automating. This post takes it from there into your code: the task the extension exports, the call that solves it, and the request that uses the token. The example is a Turnstile widget on a sign-in form at `https://example.com/login`.

Installing the extension, finding detections and running a test solve are covered in the [installation guide](/blog/how-to-install-the-resolvr-extension). Detection and export are free and need no account; solving needs resolvr API access and a proxy.

## Copy the task from the detection

Open the detection in the popup. Two parts of that view matter for your code: the fields it captured, and the **Export** section a short scroll below them.

![The popup's captured fields for a Turnstile widget next to its Export section with the JSON, Agents, curl, TS and Python buttons and a task JSON preview](/media/blog/from-detection-to-integration/capture-to-export.png "The real popup: captured fields on the left, the export they produce on the right.")

1. **Page URL** and **Site key** are required, marked `*`. They become `url` and `params.sitekey`.
2. **Action** and **cData** are optional. A dash means the widget sets none, so the task leaves it out. Do not invent one.
3. One click per format. **JSON**, **curl**, **TS** and **Python** copy to the clipboard; **Agents** saves a Markdown brief as `resolvr-cloudflare_turnstile.md`.
4. The task as it will be sent. Everything your code needs is in this object.

| You are writing | Use |
| --- | --- |
| TypeScript | **TS**, a `client.solve()` call with the task filled in |
| Python or a shell script | **Python** or **curl**, which create the task and poll for the result |
| Anything else over HTTP | **JSON**, the request body for `POST /v1/task` |
| Nothing yourself, an agent is | **Agents**, the brief described [below](#hand-it-to-a-coding-agent) |

> Exports never contain your API key, and proxy credentials read `REDACTED`. With no proxy saved in the extension there is no `proxy` field at all, and every resolvr task needs one. Your code supplies both from the environment.

## Solve it from your code

This is the **TS** export with the two secrets moved to environment variables and the form submission added. `solve()` creates the task and polls until it finishes.

```typescript
import { ResolvrClient } from "@resolvrlabs/sdk";
import { ProxyAgent, fetch } from "undici";

// Residential, with a sticky session: the form must leave from the IP that solved.
const proxy = process.env.RESOLVR_PROXY!;
const resolvr = new ResolvrClient({ apiKey: process.env.RESOLVR_API_KEY! });

export async function signIn(email: string, password: string) {
  // Solve right before submitting: the token is single-use and lasts about 300 seconds.
  const { data } = await resolvr.solve({
    type: "cloudflare_turnstile",
    url: "https://example.com/login",
    proxy,
    params: { siteKey: "1x00000000000000000000AA", data: { action: "login" } },
  });

  const res = await fetch("https://example.com/login", {
    method: "POST",
    dispatcher: new ProxyAgent(proxy),
    body: new URLSearchParams({ email, password, "cf-turnstile-response": data.token }),
  });
  if (!res.ok) throw new Error(`sign-in rejected: HTTP ${res.status}`);
  return res;
}
```

Four things decide whether this works:

- **The task is the export, unchanged.** Same `url`, same site key, same `action`. The SDK spells it `siteKey`; the JSON body spells it `sitekey`.
- **One proxy for both requests.** The token is bound to the IP that solved it. A rotating proxy hands the form request a new IP and the token is refused, so use a sticky session.
- **The token goes where the widget would have put it.** A standard Turnstile form posts it as `cf-turnstile-response`. If your target uses another name, the form request in your browser's network tab shows it.
- **Two ways to fail.** A failed task reports `status: "failed"` with an `error` such as `ERROR_IP_BANNED` or `ERROR_PROXY`. A rejected submission is your target answering no. Keep them apart in your logs, and do not retry a failed task in a loop.

Without the SDK the flow is two calls: `POST /v1/task` with the exported JSON, then `GET /v1/task/{task_id}` until `status` is `completed` or `failed`. The GET long-polls for up to 5 seconds, so give your client a read timeout of 10 or more and do not sleep between calls. The **Python** and **curl** exports already do this; the [API overview](/docs) and the [Turnstile reference](/docs/cloudflare) have the details.

## Hand it to a coding agent

The **Agents** export is a brief an agent can implement from without seeing the page. It contains the goal, the detected fields, the task JSON, how to create and poll the task, the result shape, and the replay rules for this challenge type.

The brief says what to call. It does not say where in your project, so attach it and add a prompt in the same shape. **Copy** takes the whole prompt; replace the first line of **Goal** with your own function:

```text
# resolvr integration task — Turnstile

Attached: `resolvr-cloudflare_turnstile.md`, the task brief exported by the resolvr browser extension. Its task JSON, endpoints and replay rules are the specification. Where this prompt and the brief disagree, the brief wins.

## Goal

Make `signIn()` in `src/signIn.ts` pass the Turnstile check: solve the challenge with resolvr right before the form is submitted, send the token with the form, and report whether the sign-in itself succeeded.

## Inputs

| Input | Value |
|---|---|
| API key | env `RESOLVR_API_KEY` |
| Proxy | env `RESOLVR_PROXY`, a residential proxy URL with a sticky session |
| Task | the JSON under "Create the task" in the brief. Its `proxy` is a redacted placeholder: take the real one from `RESOLVR_PROXY` |
| Casing | the JSON body is snake_case (`sitekey`); the TypeScript SDK's typed task is camelCase (`siteKey`) |
| Token field | `cf-turnstile-response`, unless the form posts a different name |

## Implement

1. Add one function, `solveTurnstile()`, that creates the task and returns `data.token`. In TypeScript use `@resolvrlabs/sdk`: `client.solve(task)` creates the task and polls correctly on its own, so do not wrap it in a loop. In any other language call `POST /v1/task`, then `GET /v1/task/{task_id}`, as the brief describes.
2. Call it immediately before the form submission. Never at startup, never cached, never reused.
3. Send the form request through `RESOLVR_PROXY` too, so it leaves from the IP that solved the challenge. In Node use `ProxyAgent` and `fetch` from `undici`. Calls to the resolvr API do not go through the proxy.
4. Return three distinct outcomes: the task failed (with its `error` code and `message`), the submission was rejected (with the HTTP status), or the sign-in succeeded. Success is whatever the existing code treats as success; keep that check. Anything else, such as a missing environment variable, throws.

## Rules

- Follow "Replay rules" in the brief exactly. If you poll over HTTP, follow "Retrieve the result" too: a read timeout of at least 10 seconds, no sleep between polls.
- One task per challenge. Do not retry a failed task, even when the error says it is retryable. Surface it and stop.
- Send the detected parameters exactly as the brief's task JSON has them: keep what is there, add nothing.
- No secrets in code, logs or commits. Never log the token.

## Done when

- The code type-checks and `signIn()` returns one of the three outcomes on every path.
- Unit tests, with the resolvr API and the target mocked, cover a completed task, a `failed` task and a rejected submission.
- You list the files you changed, the environment variables to set and the command that runs the tests.
```

## Where to go next

Before you wire it in, a [test solve](/blog/how-to-install-the-resolvr-extension#run-a-test-solve) in the popup runs the same task once and shows its phases, timing and token. It proves the task and your proxy; it does not prove your form accepts the token. That check belongs to your code.

The [API overview](/docs) covers the task lifecycle and every error code. If you do not have a key yet, [request access](/signup).
