Skip to content
ZeroCaptcha

Cloudflare Turnstile action and cData

A Cloudflare Turnstile widget can carry two values besides its site key: an action, a short label such as login, and cData, a value such as a session ID. When a widget sets them, Cloudflare returns both to the site when it verifies the token, and many sites refuse a token whose action or cData is not the one they expect. So when the widget sets them, send both with the task, exactly as the widget sets them. When it sets neither, leave them out.

ZeroCaptcha cannot tell whether a site checks them, so it never requires them: a task without them is solved and charged like any other. The site decides.

  • The widget sets an action or cData: send it. Cloudflare’s own advice to sites is to “validate the action and hostname when specified”, and its example refuses a token whose action does not match. Treat both values as required for that page.
  • The widget sets neither: leave both out. An action the widget does not have is as wrong as a missing one.
  • The cData changes on every visit, as a session ID does: read it from the page you will submit, just before you create the task, and solve once per visit.

Cloudflare allows an action of up to 32 characters and cData of up to 255, each letters, digits, _ and - (widget configurations). ZeroCaptcha checks the same limits when you create the task.

Open the page with the widget, then its source or the developer tools’ Elements panel:

  • In the HTML: the element with the class cf-turnstile carries them as data-action and data-cdata, beside data-sitekey:

    <div class="cf-turnstile" data-sitekey="0x4AAAAAAAB1cD2eF3gH4iJ5"
    data-action="login" data-cdata="session-7f3a9c2e"></div>
  • In a script: the page passes them to turnstile.render() as the action and cData options:

    turnstile.render("#captcha", {
    sitekey: "0x4AAAAAAAB1cD2eF3gH4iJ5",
    action: "login",
    cData: "session-7f3a9c2e",
    });

Search the page’s scripts for turnstile.render when the HTML has no data-sitekey. In a browser you drive, read them from the live page: the browser automation samples do, for both kinds of widget. The Cloudflare Turnstile sitekey finder reads all three values from HTML you paste.

The task still succeeds: the token is real, and it is charged. What changes is the site’s answer when it checks the token with Cloudflare’s siteverify. The reply names the action and cData the token was solved with (server-side validation):

{
"success": true,
"challenge_ts": "2026-10-01T09:30:00.000Z",
"hostname": "shop.example.com",
"error-codes": [],
"action": "login",
"cdata": "session-7f3a9c2e"
}

A site that compares them with what its widget set refuses a token solved without them, or with other values, usually with the same error it shows for a failed check. Nothing in the token or the task tells you that this is why. If a site keeps refusing tokens that arrive in time, compare the action and cData you send with the ones in the live page first.

A value outside Cloudflare’s limits is refused when you create the task, before anything is held: validation_failed on REST, ERROR_INVALID_TASK_DATA in the createTask format and ERROR_BAD_PARAMETERS in the 2Captcha format, each naming the field.

Each format has its own names for the two fields:

Format Action cData
REST, POST /v1/tasks action cdata
createTask format metadata.action metadata.cdata
2Captcha format, in.php action data
JavaScript and Python clients action cdata
Go client Action CData
Terminal window
# websiteURL is the page with the widget; websiteKey its data-sitekey. action and cdata are the
# widget's data-action and data-cdata, or the action and cData options of turnstile.render():
# leave out any the widget does not set. To solve through your own proxy, make the type
# TurnstileTask and add "proxy": "http://user:pass@proxy.example.net:8080"; to be called when
# the task ends, add "callbackUrl": "https://hooks.example.com/zerocaptcha". The Idempotency-Key is
# your ID for this task: sending the create again with it returns the same task.
reply=$(curl -sS --fail-with-body "$ZEROCAPTCHA_API/v1/tasks" \
-H "Authorization: Bearer $ZEROCAPTCHA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: login-2026-10-01-0001" \
-d '{
"type": "TurnstileTaskProxyless",
"websiteURL": "https://shop.example.com/login",
"websiteKey": "0x4AAAAAAAB1cD2eF3gH4iJ5",
"action": "login",
"cdata": "session-7f3a9c2e"
}') || { echo "refused: $reply" >&2; exit 1; } # a problem document; its code says why
task_id=$(jq -r .id <<<"$reply")
# Read the task every 2 seconds until it ends.
while :; do
task=$(curl -sS --fail-with-body "$ZEROCAPTCHA_API/v1/tasks/$task_id" \
-H "Authorization: Bearer $ZEROCAPTCHA_KEY") || { echo "$task" >&2; exit 1; }
case $(jq -r .status <<<"$task") in
succeeded) jq -r .solution.token <<<"$task"; break ;;
failed | expired) jq -r '"\(.errorCode): \(.errorDescription)"' <<<"$task" >&2; exit 1 ;;
esac
sleep 2
done

The Cloudflare Turnstile action and cData demo carries a widget that sets both. Solve it with and without them, and its check shows the action and cData siteverify returns for each token. The Cloudflare Turnstile action and cData guide goes further into what sites use them for.