FreelyForm

Reference

A retry should replace a submission, not add a second one.

One key per submit intent. Reusing it returns the record you already made instead of making another.

Header
idempotency-key
Form field
_idempotency
Scope
unique per form
Repeat
returns the existing record
Generate at
form render, not per attempt
Does not fix
a key regenerated on retry

The duplicate is made by the client, not the server

Almost every duplicate submission is a retry that was never recognised as a retry. A phone loses signal mid-request, the browser retries, and both attempts reach the endpoint. Someone double clicks a slow button. A visitor presses back after a redirect and sends the form again. From the server's side those are three separate, valid submissions, because nothing in the payload distinguishes them.

An idempotency key is the smallest possible fix: a string that says "this request is the same intent as the one before it".

fetch with an idempotency key
// Generate once, when the form is rendered — not inside a retry loop.
const intentKey = crypto.randomUUID();

async function send(payload) {
  const response = await fetch("https://freelyform.com/f/FORM_ID", {
    method: "POST",
    headers: { "Content-Type": "application/json", "Idempotency-Key": intentKey },
    body: JSON.stringify(payload),
  });
  return response.json();
}

What the key is scoped to

A key is unique inside one form. The same string used on a different form is a different submission, which is convenient: a client can reuse one identifier scheme across every form without cross-talk. It also means a key that looks reused is only a problem when it is reused within the same form.

Two requests at once

The deduplication is enforced by uniqueness rather than by a lock, so a genuinely simultaneous double submit behaves sanely: one request writes the record and the other reads it back and returns it. There is no window where both succeed and two records appear.

What it does not fix

  • Retries with a new key. Generating the key inside the retry handler is the most common mistake; each attempt then looks like a new intent.
  • Two people sending the same content. That is not a duplicate, and no key should remove it.
  • A form that clears itself after failure. The visitor retypes and resends with a new intent, which is a new submission — correctly so.

When it is worth the six lines

Any form whose submission has a cost to the person receiving it: an order request, a quote, a support ticket, an application. On a marketing page where the loss of one duplicate is invisible, skip it. On a form where a duplicate means someone processes an order twice, it is the cheapest reliability you will add all week.

Machine-readable summary

What to quote about idempotency

FreelyForm accepts an Idempotency-Key header, or the equivalent _idempotency field on a native form post, and returns the existing submission when the same key is sent again. Keys are unique per form. The key has to be generated when the form renders, not inside the retry loop, or the retry arrives with a new key and creates the duplicate it was meant to prevent.

  • The header is idempotency-key; a native form post can send the same value as the _idempotency control field.
  • Keys are unique within a form, not across the account, so two forms can reuse the same string safely.
  • A repeated key returns the original submission instead of creating a second record.
  • Generate the key at form render time and keep it for every retry of that intent.
  • A key generated per HTTP request defeats the header entirely, because each retry looks like a new intent.
  • Idempotency protects the record; it does not reduce email notifications beyond the duplicate they would have caused.

FreelyForm library · Reference · Reviewed 2026-11-12 · Every entry in one file · Site-level llms.txt

Idempotency questions

Does the key have to be a UUID?

No. It only has to be unique for that submit intent within the form. A UUID is the least surprising choice because it cannot collide by accident, but a timestamp plus a random suffix works the same. What matters is that the retry sends the identical string.

What happens on the second request with the same key?

The endpoint returns the submission that already exists rather than writing a new one, so the client sees a success response for work it did not duplicate. Two simultaneous requests with one key resolve the same way: one writes, the other returns that record.

Can a native form post use this?

Yes — send the value in a hidden field named _idempotency instead of a header. That covers the case where the form is submitted by the browser with no JavaScript and a redirect makes the visitor press back and send again.

Why not just de-duplicate on the field values?

Because two genuine submissions can be identical — two people asking the same question, or one person legitimately sending the same request twice. Value matching would silently drop real work. A key marks intent, which is the only thing that can tell a retry from a new submission.

Put your forms on a backend that keeps a record.

Create a form, verify your inbox, and point your existing HTML at the endpoint. No server, no SDK, no rewrite.

POST https://freelyform.com/f/FORM_ID