# Move from Cloudflare Turnstile to Wicket

> Change the script address, the widget's class name and the verify address to move a site from Turnstile to Wicket, and switch without a gap.

Source: https://www.coritan.com/docs/wicket/migrate-from-turnstile/

Wicket speaks Cloudflare Turnstile's protocol, so a site that uses Turnstile moves by changing three things: the script address, the class name on the widget's container and the verify address. The form field and the JavaScript global change name too. The options, the callbacks and the answer your server reads keep their shape. This page lists each change, what stays the same, and an order to switch in that leaves no form unprotected.

## Before you begin

- Add a Wicket widget for the site, with the same hostnames as the Turnstile widget and the mode that matches ([Add a widget](/docs/wicket/add-a-widget/)). Turnstile's Managed, Non-Interactive and Invisible widgets match Wicket's **Managed**, **Non-interactive** and **Invisible**.
- Copy the secret key when the page shows it. We show it once, and a lost key is replaced by rotating it ([Rotate the secret key](/docs/wicket/widget-settings/#rotate-the-secret-key)).
- Find where your site loads the Turnstile script and where your server verifies a token. Search your code for `turnstile` and `cf-turnstile`.

## The changes

| What | Turnstile | Wicket |
| --- | --- | --- |
| Script address | `https://challenges.cloudflare.com/turnstile/v0/api.js` | `https://wicket.coritan.com/v1/api.js` |
| Class on the container | `cf-turnstile` | `wicket-widget` |
| Verify address | `https://challenges.cloudflare.com/turnstile/v0/siteverify` | `https://api.coritan.com/api/v1/wicket/siteverify` |
| Form field | `cf-turnstile-response` | `wicket-response` |
| JavaScript global | `window.turnstile` | `window.wicket` |
| Site key | Starts with `0x` | Starts with `wk_` |
| Secret key | Starts with `0x` | Starts with `wks_` |

The **Install** tab of your widget shows the exact script address and verify address for the widget ([Install a widget](/docs/wicket/install-a-widget/)).

## Switch without a gap

A visitor who loaded a page before you changed it still holds a Turnstile token for as long as the page stays open, and a cache may keep serving the old page. Your server therefore verifies both kinds of token for an hour.

1. Allow Wicket in your Content-Security-Policy, if your site sends one. Add `https://wicket.coritan.com` to `script-src` and `frame-src`, and keep Cloudflare's entries ([Allow Wicket in a Content Security Policy](/docs/wicket/add-wicket-to-your-site/#allow-wicket-in-a-content-security-policy)).
2. Change your server so it reads both fields. It verifies `wicket-response` at the Wicket address with the Wicket secret key, and `cf-turnstile-response` at Turnstile's address with the Turnstile secret key:

   ```js
   const wicketToken = form["wicket-response"];
   const turnstileToken = form["cf-turnstile-response"];

   const passed = wicketToken
     ? await verifyWithWicket(wicketToken)
     : await verifyWithTurnstile(turnstileToken);
   ```

   `verifyWithWicket` is the call in [Verify a token on your server](/docs/wicket/verify-tokens/), and it posts to `https://api.coritan.com/api/v1/wicket/siteverify`:

   ```bash title="Verify a Wicket token"
   curl https://api.coritan.com/api/v1/wicket/siteverify \
     --data-urlencode "secret=$WICKET_SECRET_KEY" \
     --data-urlencode "response=$TOKEN"
   ```

3. Deploy the server, and check that forms that still use Turnstile are accepted.
4. Change the page. Replace the script address, change the class from `cf-turnstile` to `wicket-widget`, and put the Wicket site key in `data-sitekey`:

   ```html
   <script src="https://wicket.coritan.com/v1/api.js" async defer></script>
   <div class="wicket-widget" data-sitekey="wk_..." style="min-height: 65px"></div>
   ```

   The `min-height` keeps the page from moving when the widget appears, and is `140px` for a Compact widget. When your Content-Security-Policy forbids inline styles, put it in a stylesheet instead ([Reserve the widget's height](/docs/wicket/add-wicket-to-your-site/#reserve-the-widgets-height)).

5. If your own script calls `turnstile.render`, `turnstile.reset` or any other function, call `wicket.render`, `wicket.reset` and the others instead ([Widget options](/docs/wicket/widget-options/#functions)).
6. Load the page and submit the form. The widget should show **Success**, and the verify call should answer `"success": true`.
7. Wait an hour. Then remove the Turnstile verify call, the Turnstile secret key and the Cloudflare entries in your Content-Security-Policy.

To test before step 4 goes live, use a [test key](/docs/wicket/test-keys/) on a development copy of the page. Turnstile's own test keys do not work with Wicket.

> [!TIP]
> To keep your server reading `cf-turnstile-response`, set `data-response-field-name="cf-turnstile-response"` on the container. You still change the verify address. During the hour of overlap the two kinds of token would then arrive in the same field and your server could not tell them apart, so use `wicket-response` until Turnstile is gone.

## What stays the same

| What | On Wicket |
| --- | --- |
| Widget options and `data-` attributes | The same names and defaults, such as `theme`, `size`, `language`, `action`, `cdata`, `appearance`, `execution`, `retry`, `refresh-expired` and `response-field-name`. Wicket adds a few of its own ([Widget options](/docs/wicket/widget-options/)). |
| Callbacks | `callback`, `error-callback`, `expired-callback`, `timeout-callback`, `before-interactive-callback`, `after-interactive-callback` and `unsupported-callback`. |
| Functions | `render`, `execute`, `reset`, `remove`, `getResponse`, `isExpired` and `ready`. |
| The script's parameters | `render=explicit` and `onload`. |
| The verify call's fields | `secret`, `response`, `remoteip` and `idempotency_key`, sent form-encoded, as multipart form data or as JSON. |
| The verify answer | `success`, `challenge_ts`, `hostname`, `error-codes`, `action`, `cdata` and `metadata`. |
| The error codes of the verify call | `missing-input-secret`, `invalid-input-secret`, `missing-input-response`, `invalid-input-response`, `bad-request`, `timeout-or-duplicate` and `internal-error`. |
| The token | It works once, for 300 seconds, and is at most 2048 characters. |
| `idempotency_key` | A repeat of a call with the same key returns the first answer ([Repeat a call safely](/docs/wicket/verify-tokens/#repeat-a-call-safely)). |
| Hostnames | A hostname covers itself and every name under it, so `example.com` covers `www.example.com`. |

## What differs

| What | On Wicket |
| --- | --- |
| The secret key | We show it once, when you add the widget and when you rotate it, and keep only a fingerprint. Copy it then. |
| Test keys | Wicket has its own test site keys and secret keys, with their own names ([Test keys](/docs/wicket/test-keys/)). |
| The codes your `error-callback` receives | Words such as `network` and `failed`, listed in [Widget options](/docs/wicket/widget-options/#error-codes). |
| `metadata` in the verify answer | `interactive` and `event`. Store `event` to report a check later ([Verify a token on your server](/docs/wicket/verify-tokens/#report-what-a-check-turned-out-to-be)). |
| The verify call's budget | A widget may make 6000 calls a minute, and a call past it answers HTTP 429 with `rate-limited` ([Limits](/docs/wicket/limits/#rate-budgets)). |
| Plan limits | Your plan sets how many widgets you have and how many hostnames each lists ([Limits](/docs/wicket/limits/#plan-limits)). |

## Result

Visitors pass the Wicket widget, and your server accepts forms on the strength of `wicket-response` alone. The **Analytics** tab of the widget counts the challenges, and **Valid** under **Tokens verified** rises with **Passed** ([Read a widget's analytics](/docs/wicket/widget-analytics/#tokens-verified)).

## Troubleshooting

The widget shows **This site's check is not set up for this page** with `unknown_sitekey`
: The page still has the Turnstile site key. Put the widget's Wicket site key, which starts with `wk_`, in `data-sitekey`.

The widget does not appear
: The container may still have the class `cf-turnstile`, or your Content-Security-Policy may block `https://wicket.coritan.com`. [Troubleshoot Wicket](/docs/wicket/troubleshooting/#the-widget-does-not-appear) lists the causes.

The verify call answers `invalid-input-secret`
: The call sends the Turnstile secret key to the Wicket address. Send the widget's secret key, which starts with `wks_`.

The verify call answers `invalid-input-response`
: The call sends a Turnstile token to the Wicket address. Read the token from `wicket-response` for Wicket and from `cf-turnstile-response` for Turnstile.

The console says `turnstile is not defined`
: A script on your page still calls `turnstile`. Call `wicket` instead.

## Related

- [Add Wicket to your site](/docs/wicket/add-wicket-to-your-site/)
- [Verify a token on your server](/docs/wicket/verify-tokens/)
- [Widget options](/docs/wicket/widget-options/)
- [Test keys](/docs/wicket/test-keys/)

## Next steps

- [Read a widget's analytics](/docs/wicket/widget-analytics/) to see the challenges and verified tokens after the switch.
- [Change a widget's settings](/docs/wicket/widget-settings/) to add hostnames or rotate the secret key.
- [Check the limits of your plan](/docs/wicket/limits/) for widgets and hostnames.
