Documentation
The form snippet
Replace frm_your_form_id with the id we send you. The Turnstile
site key below is already correct — it is shared across all Submit Kit
forms, and is public by design.
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form action="https://api.submit-kit.com/f/frm_your_form_id" method="post">
<label>Name <input name="name" maxlength="100" required></label>
<label>Email <input name="email" type="email" maxlength="254" required></label>
<label>Message <textarea name="message" maxlength="5000" required></textarea></label>
<input name="company_website" type="text" tabindex="-1" autocomplete="off" hidden>
<div class="cf-turnstile" data-sitekey="0x4AAAAAAEi9UrROlKiU5IMN"></div>
<button type="submit">Send message</button>
</form>
Leave the company_website input in place and leave it empty. It is
a honeypot: it is hidden from people, and bots that fill it in are quietly
discarded.
If your site sends a Content-Security-Policy
Four directives matter. Each one, omitted, produces a different and misleading symptom — so it is worth copying the whole block.
script-src 'self' https://challenges.cloudflare.com;
frame-src https://challenges.cloudflare.com;
form-action 'self' https://api.submit-kit.com;
connect-src 'self' https://api.submit-kit.com;
| If you omit | What you see |
|---|---|
script-src / frame-src |
The verification checkbox never appears at all. |
form-action 'self' |
The email is delivered, but the browser hangs after submitting and never reaches your thank-you page. Browsers check this against the redirect target, not just where the form posts. |
connect-src |
Only affects the JavaScript method. The request is blocked before it leaves the page and the browser reports a generic network error, so it looks like our server is down. |
Two ways to handle the response
Two ways to handle the response
Pick one. The first needs no JavaScript at all; the second keeps the visitor on the page. Both are complete — copy either one whole.
1. Redirect to your own thank-you page
The default. On success we redirect the browser to the URL you configured, which must be a page on one of your own registered domains. Nothing else to write, and it works with JavaScript disabled.
Build a real thank-you page rather than sending people back to the form. Redirecting to the same page leaves them staring at their own filled-in fields with no sign anything happened, which reads as failure.
2. Stay on the page with JavaScript
Send Accept: application/json and you get a JSON reply instead
of a redirect. This example handles the cases that actually occur, and the
turnstile.reset() call is not optional — see below.
<form id="contact-form" action="https://api.submit-kit.com/f/frm_your_form_id" method="post">
<label>Name <input name="name" maxlength="100" required></label>
<label>Email <input name="email" type="email" maxlength="254" required></label>
<label>Message <textarea name="message" maxlength="5000" required></textarea></label>
<input name="company_website" type="text" tabindex="-1" autocomplete="off" hidden>
<div class="cf-turnstile" data-sitekey="0x4AAAAAAEi9UrROlKiU5IMN"></div>
<button type="submit">Send message</button>
<p id="form-status" role="status" aria-live="polite"></p>
</form>
<script>
(function () {
var form = document.getElementById("contact-form");
var status = document.getElementById("form-status");
var button = form.querySelector("button[type=submit]");
var label = button.textContent;
form.addEventListener("submit", function (event) {
event.preventDefault();
button.disabled = true;
button.textContent = "Sending\u2026";
status.textContent = "";
fetch(form.action, {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form)
})
.then(function (r) { return r.json(); })
.then(function (body) {
if (body.ok) {
form.innerHTML = "<p role='status'>Thanks \u2014 your message has been sent.</p>";
return;
}
status.textContent = messageFor(body);
recover();
})
.catch(function () {
status.textContent = "We could not reach the server. Please try again.";
recover();
});
});
function messageFor(body) {
if (body.error === "VALIDATION_FAILED" && body.fields && body.fields.length) {
return "Please check: " + body.fields.map(function (f) { return f.field; }).join(", ");
}
return body.message || "Your message could not be sent. Please try again.";
}
function recover() {
button.disabled = false;
button.textContent = label;
if (window.turnstile) window.turnstile.reset(); // see below
}
})();
</script>
Always reset Turnstile after a failure
A Turnstile token is spent the moment we verify it. If a submission fails
for any reason and the visitor presses send again, the second attempt
carries the same, now-used token and is rejected as a replay — so they see
"we could not verify that you are human" no matter how many times
they try. Calling turnstile.reset() issues a fresh challenge and
makes retrying work.
The same applies without JavaScript: after an error, the visitor must reload the form rather than press the back button, because the cached page still holds the spent token. Our error pages say so.
Why the button changes to "Sending…"
A submission takes around a second: we verify the visitor is human and hand the message to our mail provider before telling you it was sent. A button that looks inert for that second reads as broken, and people click it twice.
Field types
text— single line, up to 500 charactersemail— validated address, up to 254 characterstextarea— multi-line, up to 5,000 characters
A form may declare up to 20 fields, and the whole request must be under 32 KB. Fields you have not declared are discarded rather than rejected.
Errors
JSON responses carry a stable error code:
VALIDATION_FAILED(400) — includes which fields failed and why, never their valuesORIGIN_REJECTED,FORM_DISABLED,BOT_REJECTED(403)FORM_NOT_FOUND(404)PAYLOAD_TOO_LARGE(413),UNSUPPORTED_MEDIA_TYPE(415)RATE_LIMITED(429) — with aRetry-AfterheaderDELIVERY_FAILED(502)TURNSTILE_UNAVAILABLE,MAINTENANCE(503) — both worth retrying
Changing your configuration
Recipients, allowed domains, fields, and the success URL are all held in server-side configuration. Email support@submit-kit.com and we will update and redeploy.