Protection

Protect your forms from unwanted submissions.

Local controls, server credentials, published rules, and workspace authorization.

Enhanced protection

Keep a Standard website connection for ordinary HTML without JavaScript. For signed, expiring, single-use form attempts, choose Enhanced under Website connection → Browser protection in the setup guide, or edit the connection in Sources → Connection protection.

Add this script once to the page containing your existing form. Your form keeps its field names and styling. The script automatically finds POST forms whose action is a FormBastion submission endpoint.

Optional protection script
<script src="https://cdn.formbastion.com/bastion.js?v=2" defer></script>
  1. Copy your endpoint into the existing form’s action and use method="POST".
  2. Add the script. For an already live form, add it before enabling Enhanced protection.
  3. Save Enhanced on that connection. This setting takes effect immediately; field rules and optional repeat controls take effect when published.
  4. Send a test from your website, then open Connect & test and choose Check for a response. An accepted response with verified browser protection confirms installation. Tests use normal submission allowance and run enabled webhooks.

The script requests an intent without sending field values, then adds a signed token and randomized, hidden honeypot. A new attempt must be at least three seconds old; the script handles that short wait. Tokens expire after two hours and refresh when needed before sending. Tokens are bound to the connection, its allowed origins, and published version.

Submission happens in the background. On acceptance, the visitor opens your configured HTTPS thank-you page or the FormBastion receipt page. On an error, values remain in the form and the visitor can retry. An unchanged retry within 24 hours uses the same idempotency key, including when an acceptance response was lost. Changing submitted values creates a new attempt. Avoid reloading the page during an uncertain request: retry state lives only in that page’s memory.

To stay on the same page after success, add data-formbastion to your form. This existing AJAX option shows a receipt message and resets the form after acceptance. You can supply a data-formbastion-status element for messages, or let the script add one. Multiple and dynamically inserted forms are supported independently.

Enhanced connections require working JavaScript.

Removing the script or omitting its token cannot bypass the server checks. Keep Standard enabled if visitors must submit without JavaScript. A browser token adds friction to automated abuse; it does not prove a human identity, verify the submitted email address, or stop every bot. CAPTCHA and external reputation checks are not included.

Our own contact form

The FormBastion contact page uses the same Enhanced token checks: signed, expiring, single-use attempts, a three-second minimum age, and a randomized honeypot. We render its protection fields on our server, so this first-party form also works without JavaScript. Customer website connections still use the CDN script described above. Support messages go to our separate support inbox; they do not use your workspace allowance.

Custom JavaScript forms

If your code already handles submission, set data-formbastion-mode="manual" on the form and call FormBastion.prepare(form, event.submitter) before sending. Use the returned formData and endpoint; do not construct a separate body that drops the protection fields. The script does not replace your custom handler. Load your handler after the CDN script, for example with two ordered deferred scripts.

Custom handler · existing form and live status element
const form = document.querySelector('#contact');
const status = form.querySelector('[role="status"]');
let sending = false;
form.addEventListener('submit', async event => {
  event.preventDefault();
  if (sending) return;
  sending = true;
  status.textContent = 'Preparing your form…';
  try {
    const attempt = await FormBastion.prepare(form, event.submitter);
    const response = await fetch(attempt.endpoint, {
      method: 'POST', credentials: 'omit',
      headers: { Accept: 'application/json' },
      body: attempt.formData
    });
    const result = await response.json();
    if (!response.ok || !result.ok) {
      if (['form_token_invalid', 'form_replayed', 'form_protection_missing'].includes(result.error?.code)) {
        await FormBastion.refresh(form);
      }
      throw new Error(result.error?.message || 'Please try again.');
    }
    FormBastion.complete(form);
    form.reset();
    status.textContent = 'Your message was received. Thank you.';
  } catch (error) {
    status.textContent = error.message + ' Your values are still here.';
  } finally {
    sending = false;
  }
});

prepare() returns endpoint, formData, idempotencyKey, and protected. Call it for each attempt; it refreshes stale tokens and preserves the retry key for unchanged data. Call complete(form) only after confirmed acceptance. If your server reports form_replayed or form_token_invalid after a manual request, call FormBastion.refresh(form) before the next attempt; this retains the idempotency key and entered values.

Automatic mode emits formbastion:ready, formbastion:error, and formbastion:received. Cancel the received event with event.preventDefault() to handle successful navigation yourself. Forms with submit buttons that override the action or method should use manual mode. The script accepts only HTTPS FormBastion endpoint actions, never arbitrary destinations.

Optional repeat controls

In Protection & files, choose a configured, required Email field and a maximum number of accepted responses per email per hour. Set the limit to 0 to leave it off. The hourly window resets on the hour in UTC. Email comparisons ignore letter case and surrounding whitespace; the limit does not verify ownership of the submitted address.

You can also block matching content for a chosen window, from one minute to 24 hours in the interface. Matching compares all submitted business fields after normalizing letter case and whitespace, plus exact attachment names and contents. It can reject legitimate repeated messages, so leave it off when repeats are expected. Both controls work with Standard, Enhanced, and trusted-server connections, and apply independently to each connection. Save and publish to activate them.

Only committed inbox admissions consume these optional limits. Quarantined requests are held without consuming these counters. An authorized manual release from quarantine is a review decision and does not rerun repeat controls. Retrying an existing receipt with the same idempotency key returns that receipt before evaluating a new token or repeat limit.

Initialization and troubleshooting

The script uses GET https://submit.formbastion.com/f/{source_public_key}/intent. It returns protection.required: false for Standard HTML, or a signed token, randomized honeypot name, readiness delay, expiry, and configured redirect for Enhanced. It accepts no submission data, creates no customer session, and uses no submission allowance. The source must be active and published, with an active verified workspace owner. Initialization is rate limited separately.

With a Content Security Policy, allow https://cdn.formbastion.com in script-src and https://submit.formbastion.com in connect-src. Keep the submit host in form-action for ordinary HTML. Use multipart/form-data for native attachment forms. Do not set a multipart Content-Type yourself when using FormData.

Configure exact allowed origins, including your development origin when needed. Enhanced connections with an origin allowlist reject both missing and unmatched Origin headers. Origin headers and public tokens are not authentication. Use a trusted-server connection when a private secret is required.

CodeNext step
form_too_fast · 425Wait for the three-second readiness delay.
form_token_invalid / form_protection_missing · 422Refresh protection, keep the entered values, and retry.
form_replayed · 409Retry an accepted attempt with its original key, or prepare a fresh token for a new response.
email_rate_limited · 429Wait for the next hourly window; Retry-After reports the remaining seconds.
duplicate_content · 409A matching response already arrived within the configured window.
initialization_rate_limited · 429Wait a minute before requesting another intent.

Choose the source mode

HTML sources are for public browser forms. Their public keys identify a source and must be assumed visible. Server sources add a source-specific secret that belongs only on a trusted server. Never put that secret in browser code.

Publish your validation rules

Define required fields, accepted types, and maximum lengths in your form draft. Supported field types include text, email, number, and URL. Decide whether fields outside the configured schema should be rejected. Save your draft before publishing. The editor protects unsaved changes and disables publication until the draft is saved. Publish the saved draft to make the rules active.

Client-side HTML validation improves the form experience but does not replace server enforcement. Reserved metadata cannot change the workspace, configured destinations, or action credentials.

Honeypot quarantine

The default honeypot field is _gotcha. Legitimate users should leave it empty. A non-empty value is stored in quarantine, with no normal processing until an authorized customer reviews and releases it.

Rate limits and bounded input

Source-level rate limits and bounded field/file sizes limit resource use. Rate controls use the local Form Bastion protection foundation. They are useful controls, not a promise to detect every abusive submission.

Allowed origins

Configure the origins permitted to use a browser source when your integration needs origin checks and CORS. Origins include scheme and hostname, and a port when applicable. A browser origin restriction is not server authentication; non-browser clients can construct their own headers.

Keep secrets in the right place

  • Workspace API keys belong in server-side integrations.
  • Server-source secrets authenticate that source, not the management API.
  • Webhook signing secrets belong only in the configured action and receiving server.
  • Account passwords, MFA secrets, and recovery codes are never form fields.

Read the platform security overview

Continue exploringWebhook actions