Tutorials

Compensating Transactions for Failed CAPTCHA Workflows

A multi-step workflow creates an account (step 1), solves a CAPTCHA (step 2), submits a form (step 3), and verifies email (step 4). Step 3 fails with a server error. Now you have a solved CAPTCHA token you paid for, a half-created account, and no form submission. Compensating transactions undo completed steps in reverse order to keep your system consistent.

When You Need Compensation

Unlike database transactions, CAPTCHA solves can't be rolled back — the API already processed the task. Compensation means undoing the effects of prior steps:

Step Action Compensation
Create account POST to registration endpoint Delete or deactivate account
Solve CAPTCHA Submit to CaptchaAI Report incorrect (reclaim credit)
Submit form POST form data No-op or flag for manual cleanup
Store result Insert into database Delete the record

Python: Saga with Compensation

import requests
import time
from dataclasses import dataclass, field

API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"


@dataclass
class StepResult:
    name: str
    data: dict = field(default_factory=dict)
    compensated: bool = False


class Saga:
    """Execute a sequence of steps with automatic compensation on failure."""

    def __init__(self):
        self._steps: list[tuple[str, callable, callable]] = []
        self._completed: list[StepResult] = []

    def add_step(self, name: str, execute: callable, compensate: callable):
        """Add a step with its compensation action."""
        self._steps.append((name, execute, compensate))

    def run(self, context: dict) -> dict:
        """Execute all steps. On failure, compensate in reverse order."""
        for name, execute, compensate in self._steps:
            try:
                result = execute(context)
                step_result = StepResult(name=name, data=result or {})
                self._completed.append(step_result)
                context[name] = result or {}
                print(f"[SAGA] ✓ {name}")
            except Exception as e:
                print(f"[SAGA] ✗ {name} failed: {e}")
                self._compensate(context)
                raise SagaFailedError(
                    f"Step '{name}' failed: {e}",
                    completed=[s.name for s in self._completed],
                    compensated=[s.name for s in self._completed if s.compensated],
                ) from e

        return context

    def _compensate(self, context: dict):
        """Run compensation actions in reverse order."""
        for name, _, compensate in reversed(self._steps):
            matching = [s for s in self._completed if s.name == name]
            if not matching:
                continue

            try:
                compensate(context)
                matching[0].compensated = True
                print(f"[SAGA] ↩ Compensated: {name}")
            except Exception as e:
                print(f"[SAGA] ⚠ Compensation failed for {name}: {e}")


class SagaFailedError(Exception):
    def __init__(self, message: str, completed: list[str], compensated: list[str]):
        super().__init__(message)
        self.completed = completed
        self.compensated = compensated


# --- Step implementations ---

def solve_captcha(context: dict) -> dict:
    """Step: Solve the CAPTCHA via CaptchaAI."""
    params = {
        "key": API_KEY, "json": 1,
        "method": "turnstile",
        "sitekey": context["sitekey"],
        "pageurl": context["pageurl"],
    }
    resp = requests.post(SUBMIT_URL, data=params, timeout=30).json()
    if resp.get("status") != 1:
        raise RuntimeError(f"Submit failed: {resp.get('request')}")

    task_id = resp["request"]
    start = time.monotonic()
    while time.monotonic() - start < 180:
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY, "action": "get", "id": task_id, "json": 1
        }, timeout=15).json()

        if poll.get("request") == "CAPCHA_NOT_READY":
            continue
        if poll.get("status") == 1:
            return {"token": poll["request"], "task_id": task_id}
        raise RuntimeError(f"Solve failed: {poll.get('request')}")

    raise RuntimeError("Timeout")


def compensate_captcha(context: dict):
    """Compensation: Report the task as incorrect to reclaim credit."""
    task_id = context.get("solve_captcha", {}).get("task_id")
    if task_id:
        requests.get(RESULT_URL, params={
            "key": API_KEY, "action": "reportbad", "id": task_id, "json": 1,
        }, timeout=15)


def submit_form(context: dict) -> dict:
    """Step: Submit the form with the CAPTCHA token."""
    token = context["solve_captcha"]["token"]
    resp = requests.post(context["submit_url"], data={
        "captcha_token": token,
        "email": context["email"],
        "username": context["username"],
    }, timeout=30)

    if resp.status_code != 200:
        raise RuntimeError(f"Form submit failed: {resp.status_code}")

    return {"confirmation_id": resp.json().get("id")}


def compensate_form(context: dict):
    """Compensation: Cancel the submission if possible."""
    conf_id = context.get("submit_form", {}).get("confirmation_id")
    if conf_id:
        requests.delete(
            f"{context['submit_url']}/{conf_id}",
            timeout=15,
        )


def store_result(context: dict) -> dict:
    """Step: Store the result in the database."""
    # Simulated database insert
    record = {
        "email": context["email"],
        "confirmation_id": context["submit_form"]["confirmation_id"],
        "captcha_task_id": context["solve_captcha"]["task_id"],
    }
    print(f"[DB] Stored: {record}")
    return {"record_id": "db-123"}


def compensate_store(context: dict):
    """Compensation: Delete the database record."""
    record_id = context.get("store_result", {}).get("record_id")
    if record_id:
        print(f"[DB] Deleted record: {record_id}")


# --- Build and run the saga ---

saga = Saga()
saga.add_step("solve_captcha", solve_captcha, compensate_captcha)
saga.add_step("submit_form", submit_form, compensate_form)
saga.add_step("store_result", store_result, compensate_store)

context = {
    "sitekey": "0x4XXXXXXXXXXXXXXXXX",
    "pageurl": "https://example.com/register",
    "submit_url": "https://example.com/api/register",
    "email": "user@example.com",
    "username": "testuser",
}

try:
    result = saga.run(context)
    print(f"Saga completed: {result.get('store_result')}")
except SagaFailedError as e:
    print(f"Saga failed: {e}")
    print(f"  Completed steps: {e.completed}")
    print(f"  Compensated steps: {e.compensated}")

JavaScript: Async Saga

const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";

class Saga {
  #steps = [];
  #completed = [];

  addStep(name, execute, compensate) {
    this.#steps.push({ name, execute, compensate });
    return this;
  }

  async run(context) {
    for (const step of this.#steps) {
      try {
        const result = await step.execute(context);
        this.#completed.push(step);
        context[step.name] = result || {};
        console.log(`[SAGA] ✓ ${step.name}`);
      } catch (error) {
        console.log(`[SAGA] ✗ ${step.name}: ${error.message}`);
        await this.#compensate(context);
        throw error;
      }
    }
    return context;
  }

  async #compensate(context) {
    for (const step of [...this.#completed].reverse()) {
      try {
        await step.compensate(context);
        console.log(`[SAGA] ↩ ${step.name}`);
      } catch (e) {
        console.warn(`[SAGA] Compensation failed: ${step.name}: ${e.message}`);
      }
    }
  }
}

async function solveCaptcha(ctx) {
  const body = new URLSearchParams({
    key: API_KEY, json: "1", method: "turnstile",
    sitekey: ctx.sitekey, pageurl: ctx.pageurl,
  });
  const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
  if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);

  const taskId = resp.request;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
    const poll = await (await fetch(url)).json();
    if (poll.request === "CAPCHA_NOT_READY") continue;
    if (poll.status === 1) return { token: poll.request, taskId };
    throw new Error(`Solve: ${poll.request}`);
  }
  throw new Error("Timeout");
}

async function compensateCaptcha(ctx) {
  const taskId = ctx.solveCaptcha?.taskId;
  if (taskId) {
    await fetch(`${RESULT_URL}?key=${API_KEY}&action=reportbad&id=${taskId}&json=1`);
  }
}

async function submitForm(ctx) {
  const resp = await fetch(ctx.submitUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      captcha_token: ctx.solveCaptcha.token,
      email: ctx.email,
    }),
  });
  if (!resp.ok) throw new Error(`Form: ${resp.status}`);
  return await resp.json();
}

async function compensateForm(ctx) {
  const id = ctx.submitForm?.id;
  if (id) await fetch(`${ctx.submitUrl}/${id}`, { method: "DELETE" });
}

// Build and run
const saga = new Saga()
  .addStep("solveCaptcha", solveCaptcha, compensateCaptcha)
  .addStep("submitForm", submitForm, compensateForm);

try {
  const result = await saga.run({
    sitekey: "0x4XXXXXXXXXXXXXXXXX",
    pageurl: "https://example.com/register",
    submitUrl: "https://example.com/api/register",
    email: "user@example.com",
  });
  console.log("Success:", result.submitForm);
} catch (error) {
  console.error("Saga failed:", error.message);
}

Compensation Strategies

Strategy Use when Example
Report bad CAPTCHA token was unused Call reportbad to reclaim credit
Delete record Database row was created DELETE the row
Status update Can't delete but can invalidate Mark record as cancelled
Notification Manual intervention needed Send alert for failed compensation
No-op Step has no reversible effect Skip compensation (logging, metrics)

Troubleshooting

Issue Cause Fix
Compensation itself fails Target service down Log for manual cleanup; don't retry infinitely
reportbad doesn't refund Task was already accepted as correct Report before the token is used
Partial compensation Middle step's compensation threw Handle each compensation independently — don't stop on first error
Context missing required data Step didn't store identifiers Always return IDs and references from each step
Compensation runs twice Saga retried after first compensation Use idempotent compensation actions

FAQ

Should I always report CAPTCHA tasks as bad during compensation?

Only report if the token wasn't used. If the form submission failed for a reason unrelated to the CAPTCHA (server error, validation failure), the solve was technically correct. Reporting correct solves as bad can affect your account reputation.

How do I handle compensation failures?

Log the failure and continue compensating remaining steps. After the saga completes, surface uncompensated steps for manual review. Never let a compensation failure prevent other compensations from running.

Can I retry the saga instead of compensating?

Yes, for transient failures. Add retry logic before triggering compensation. Only compensate when the error is permanent or retries are exhausted. Combine with idempotency so retried steps don't create duplicates.

Next Steps

Build resilient multi-step CAPTCHA workflows — get your CaptchaAI API key and implement the saga pattern.

Related guides:

Discussions (0)

No comments yet.

Related Posts

DevOps & Scaling Ansible Playbooks for CaptchaAI Worker Deployment
Deploy and manage Captcha AI workers with Ansible — playbooks for provisioning, configuration, rolling updates, and health checks across your server fleet.

Deploy and manage Captcha AI workers with Ansible — playbooks for provisioning, configuration, rolling updates...

Automation Python All CAPTCHA Types
Apr 07, 2026
DevOps & Scaling Blue-Green Deployment for CAPTCHA Solving Infrastructure
Implement blue-green deployments for CAPTCHA solving infrastructure — zero-downtime upgrades, traffic switching, and rollback strategies with Captcha AI.

Implement blue-green deployments for CAPTCHA solving infrastructure — zero-downtime upgrades, traffic switchin...

Automation Python All CAPTCHA Types
Apr 07, 2026
Troubleshooting CaptchaAI API Error Handling: Complete Decision Tree
Complete decision tree for every Captcha AI API error.

Complete decision tree for every Captcha AI API error. Learn which errors are retryable, which need parameter...

Automation Python All CAPTCHA Types
Mar 17, 2026
Tutorials Using Fiddler to Inspect CaptchaAI API Traffic
How to use Fiddler Everywhere and Fiddler Classic to capture, inspect, and debug Captcha AI API requests and responses — filters, breakpoints, and replay for tr...

How to use Fiddler Everywhere and Fiddler Classic to capture, inspect, and debug Captcha AI API requests and r...

Automation Python All CAPTCHA Types
Mar 05, 2026
Tutorials CAPTCHA Handling in Mobile Apps with Appium
Handle CAPTCHAs in mobile app automation using Appium and Captcha AI — extract Web sitekeys, solve, and inject tokens on Android and i OS.

Handle CAPTCHAs in mobile app automation using Appium and Captcha AI — extract Web View sitekeys, solve, and i...

Automation Python All CAPTCHA Types
Feb 13, 2026
Tutorials Streaming Batch Results: Processing CAPTCHA Solutions as They Arrive
Process CAPTCHA solutions the moment they arrive instead of waiting for tasks to complete — use async generators, event emitters, and callback patterns for stre...

Process CAPTCHA solutions the moment they arrive instead of waiting for all tasks to complete — use async gene...

Automation Python All CAPTCHA Types
Apr 07, 2026
Reference CaptchaAI CLI Tool: Command-Line CAPTCHA Solving and Testing
A reference for building and using a Captcha AI command-line tool — solve CAPTCHAs, check balance, test parameters, and integrate with shell scripts and CI/CD p...

A reference for building and using a Captcha AI command-line tool — solve CAPTCHAs, check balance, test parame...

Automation Python All CAPTCHA Types
Feb 26, 2026
DevOps & Scaling Auto-Scaling CAPTCHA Solving Workers
Build auto-scaling CAPTCHA solving workers that adjust capacity based on queue depth, balance, and solve rates.

Build auto-scaling CAPTCHA solving workers that adjust capacity based on queue depth, balance, and solve rates...

Automation Python All CAPTCHA Types
Mar 23, 2026
DevOps & Scaling CaptchaAI Monitoring with Datadog: Metrics and Alerts
Monitor Captcha AI performance with Datadog — custom metrics, dashboards, anomaly detection alerts, and solve rate tracking for CAPTCHA solving pipelines.

Monitor Captcha AI performance with Datadog — custom metrics, dashboards, anomaly detection alerts, and solve...

Automation Python All CAPTCHA Types
Feb 19, 2026
Tutorials Pytest Fixtures for CaptchaAI API Testing
Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI.

Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI. Covers mocking, live integra...

Automation Python reCAPTCHA v2
Apr 08, 2026
Tutorials GeeTest Token Injection in Browser Automation Frameworks
how to inject Gee Test v 3 solution tokens into Playwright, Puppeteer, and Selenium — including the three-value response, callback triggering, and form submissi...

Learn how to inject Gee Test v 3 solution tokens into Playwright, Puppeteer, and Selenium — including the thre...

Automation Python Testing
Jan 18, 2026