Docs/Forms/Security/Securing your endpoints

Securing your endpoints

Origin allowlisting for browsers, API-key mode for servers, plus CORS and built-in spam defense.

View as Markdown

Lanes Forms secures a submission endpoint on two independent axes: where the browser request came from (origin), and whether a server request carries a key (submission mode). Understanding the split is the key to choosing the right setup.

The two axes

  • allowed_origins governs browser submissions. A request that carries an Origin header must match the allowlist.
  • submission_auth governs non-browser submissions (a server, curl, or an agent, none of which send an Origin). It is either open (the default) or api_key, and is changed with a PATCH on a claimed form.

The important subtlety: allowed_origins applies in both modes. Switching to api_key mode does not loosen or tighten browser rules; it only decides what happens to requests with no Origin.

The full matrix

Calleropen (default)api_key
Browser, Origin in allowlistAllowed, no credentialAllowed, no credential
Browser, Origin not in allowlist403 origin_not_allowed403 origin_not_allowed
Server / curl / agent (no Origin)Allowed, no credentialRequires lfk_ key, else 401 api_key_required
localhost origin (dev)AllowedRequires lfk_ key

An empty allowed_origins rejects every browser origin, which is exactly what you want for a form that only your backend submits to.

Client (browser) interaction

For a public form on a website, the browser is the client and it cannot keep a secret. So the browser is authorized by its origin, which the browser sets and page scripts cannot forge.

  • Keep the form in open mode.
  • List the sites that may submit in allowed_origins, for example ["https://example.com"].
  • Do not put any key in the page.

A key in the browser is a leaked key

Anything shipped to the browser is readable by anyone. That is why browser forms rely on origin allowlisting instead of secrets. Reserve api_key mode for server-to-server use.

Server-to-server interaction

When only your backend submits, there is no browser and no Origin header. Lock the form down:

  • Set submission_auth to api_key.
  • Send the workspace key on every submit: Authorization: Bearer lfk_....
  • Optionally leave allowed_origins empty so no browser can submit at all.

See API keys for creating and storing the key.

CORS

CORS is handled per form, not by a global wildcard:

  • OPTIONS /v1/f/{form_id} preflight echoes the exact requesting Origin (never *), with Access-Control-Allow-Methods: POST, OPTIONS and Allow-Headers: Content-Type, Authorization. A preflight with no Origin returns 400 preflight_origin_required; a disallowed one returns 403 origin_not_allowed.
  • Every submission response for an allowed origin echoes Access-Control-Allow-Origin so page scripts can read success and error bodies. A disallowed origin is never echoed.

Built-in defenses

Beyond origins and keys, every form gets these for free:

  • Honeypot: the _gotcha field silently absorbs bots. See Submitting.
  • Rate limiting: 10 submissions per minute per IP.
  • Size cap: 64 KB per request.
  • Disposable-email rejection at provisioning time.
  • Unclaimed cap: 25 submissions before a claim is required.

IP addresses are stored only as a salted hash, never in the raw.

Next: Errors for the full error vocabulary.