Bot protection
Creating a hold is the only unauthenticated write that costs us money (an SMS) and blocks a real slot. In production it is protected by Cloudflare Turnstile.
Do you need it?
Section titled “Do you need it?”Look at the site payload:
{ "captchaSiteKey": "0x4AAAAAAA…" }- Present → attach
captchaTokentoPOST /holds, or you get400 CAPTCHA_FAILED. Outside local development it is always present, on live and sandbox sites alike: the API refuses to run without Turnstile. - Absent → only on a local development API. Send nothing.
What we check
Section titled “What we check”A token is accepted only when Cloudflare confirms it and:
- it was solved for the action
hold(passaction: 'hold'to the widget, ordata-action="hold"); - it was solved on one of the studio’s own hosts: its hosted site (
<slug>.sites.mrcharles.app) or its verified custom domain. For sandbox sites the official docs host also counts, so the examples here work.
A token solved anywhere else (another domain, another action) gets 400 CAPTCHA_FAILED. If your frontend lives on a domain that is not one of these, use the embedded booking (its iframe runs on the studio’s host) for holds.
Browser
Section titled “Browser”<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script><div class="cf-turnstile" data-sitekey="SITE_KEY_FROM_PAYLOAD" data-action="hold" data-callback="onToken"></div>let captchaToken = '';window.onToken = (token) => { captchaToken = token; }; // now the Book button can be enabled
await post(`/public/sites/${slug}/holds`, { ...payload, captchaToken });Render it explicitly if you prefer:
const id = turnstile.render('#captcha', { sitekey: site.captchaSiteKey, action: 'hold', callback: (t) => setToken(t), 'expired-callback': () => setToken(''), 'error-callback': () => setToken(''),});Rules that bite
Section titled “Rules that bite”- A token is single-use. After a failed hold (slot taken, validation error), reset the widget and get a fresh one:
turnstile.reset(id). - Tokens expire after about five minutes. If the guest fills the form slowly, refresh before submitting.
- The token is bound to the site key, not to the guest. Do not cache or share it.
Native apps and server-to-server
Section titled “Native apps and server-to-server”Turnstile is a browser widget and holds need it. There is no origin allow-list or captcha bypass for native apps or backend jobs today; open the studio’s hosted booking page or the embed in a web view instead. Do not try to automate the widget.