Ingestion Endpoint

POST /v1/f/:publicKey accepts form submissions and queues them for async delivery to all configured destinations.

Zero Storage

formdata.dev never persists submission data. The payload is held in memory only long enough to enqueue delivery messages, then discarded.

Request

Property Value
Method POST
URL https://api.formdata.dev/v1/f/:publicKey
Content-Type application/json
Max payload 128 KB

Headers

Header Required Description
Content-Type Yes Must be application/json
Origin Conditional Required if the form has allowedOrigins configured
x-captcha-token Conditional Required if the form has verifyCaptcha enabled

Body

Any valid JSON object. The entire body becomes payload in the SubmissionEnvelope delivered to your destinations.

{
  "name": "Jane Doe",
  "email": "[email protected]",
  "message": "Hello from my website!"
}

CORS

The ingestion endpoint handles CORS automatically:

  • PreflightOPTIONS /v1/f/:publicKey returns appropriate headers
  • Allowed methodsPOST only
  • Allowed headersContent-Type, x-captcha-token
  • Max age — 86400 seconds (24 hours)

If allowedOrigins is empty (the default), Access-Control-Allow-Origin is set to *. If allowedOrigins contains specific URLs, the Origin header must match one of them.

Captcha Verification

When a form has verifyCaptcha: true, every submission must include a valid captcha token in the x-captcha-token header. The token is verified server-side against the form's stored captchaSecret using the configured CAPTCHA_VERIFY_URL.

Turnstile

formdata.dev works with Cloudflare Turnstile out of the box. Set captchaSecret to your Turnstile site secret when creating the form.

Response

202 Accepted — Submission queued successfully:

{
  "ok": true,
  "submissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "queuedDestinations": 2
}

Errors

Status Error Cause
400 Invalid JSON payload Body is not valid JSON
400 Captcha token is required verifyCaptcha is enabled but x-captcha-token header is missing
400 Invalid captcha token Token failed server-side verification
403 Origin is not allowed for this form Origin header does not match allowedOrigins
404 Form endpoint not found or disabled Invalid public key or form is disabled
413 Payload too large (max 128KB) Content-Length exceeds 128 KB

SubmissionEnvelope

Every destination receives a SubmissionEnvelope containing the original payload plus metadata:

{
  "submissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "formId": "f1234567-89ab-cdef-0123-456789abcdef",
  "formName": "contact-form",
  "payload": {
    "name": "Jane Doe",
    "email": "[email protected]",
    "message": "Hello from my website!"
  },
  "metadata": {
    "origin": "https://example.com",
    "ip": "203.0.113.42",
    "userAgent": "Mozilla/5.0 ...",
    "referer": "https://example.com/contact",
    "submittedAt": "2026-03-18T12:00:00.000Z"
  }
}
Field Type Description
submissionId string Unique UUID for this submission
formId string Internal form identifier
formName string Human-readable form name
payload unknown The raw JSON body from the submission
metadata.origin string | null Origin header value
metadata.ip string | null Submitter IP from CF-Connecting-IP
metadata.userAgent string | null User-Agent header
metadata.referer string | null Referer header
metadata.submittedAt string ISO 8601 timestamp

Example

curl -X POST https://api.formdata.dev/v1/f/pk_abc123 \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe",
    "email": "[email protected]",
    "message": "Hello!"
  }'

Response (202):

{
  "ok": true,
  "submissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "queuedDestinations": 2
}

With Captcha

curl -X POST https://api.formdata.dev/v1/f/pk_abc123 \
  -H "Content-Type: application/json" \
  -H "x-captcha-token: 0.AbCdEfGh..." \
  -d '{
    "email": "[email protected]"
  }'

Browser (fetch)

const response = await fetch("https://api.formdata.dev/v1/f/pk_abc123", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    name: formData.get("name"),
    email: formData.get("email"),
    message: formData.get("message"),
  }),
});

const result = await response.json();
console.log(result.submissionId);