send.webforms.toRead the docs →

Documentation

Everything you need to ship a form.

WebForms is a form backend: set any HTML form's action to your endpoint, and each submission is spam-checked, stored and delivered to the destinations you choose. This page covers the quickstart, every reserved field, JSON submissions and delivery behaviour. The dashboard, file uploads, spam controls, webhook signing, routing, the REST APIs and the MCP server each have their own page in the sidebar.

Last updated

Quickstart

Every form gets an endpoint: https://send.webforms.to/wft_your_key. Create a free account, copy the key from your dashboard and point any form at it. No backend, no SMTP credentials, nothing to deploy.

index.html
<form action="https://send.webforms.to/wft_your_key" method="POST">
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

Submissions accept application/x-www-form-urlencoded, multipart/form-data (needed for file uploads, see file uploads) and application/json. Browser submits without an accept: application/json header get a friendly HTML success page, or a redirect if you set _next.

The same endpoint also fans out to Slack, Discord, Telegram, webhooks and more from the one submission. Configure destinations and routing rules in the dashboard, or over the write API.

Special fields

Field names starting with an underscore control behaviour and are never stored as submission data.

  • _nextRedirect URL after a successful browser submission.
  • _redirectAlias for _next, also used on validation failure to redirect back with a wf_error query param.
  • _gotchaHoneypot. Add it hidden with CSS; if it arrives filled, the submission is dropped while the bot sees a success response. Customisable per form. See spam protection.
  • _subjectOverrides the email notification subject.
  • _from_nameOverrides the sender name shown in the email notification. Only applies on plans with custom sender names (Pro and Agency); ignored on Free.
  • _replytoSets the reply-to address on the email notification (must be a valid email).
  • _pageFull URL of the page the form is on, e.g. https://yoursite.com/pricing. Shown as “Submitted from” in the notification email. A cross-origin POST's Referer header is stripped to bare origin by the browser, so set this explicitly (a hidden field with document.location.href) if you want the exact page, not just the domain.
  • _start_timeUnix ms timestamp of when the form was rendered. Powers the fill-time spam trap. Set it in a hidden field via JS on page load.
  • _testMarks the submission as a test: stored, delivered and counted toward your quota like any other submission, but left out of the default Submissions API listing.
  • _captcha / cf-turnstile-response / h-captcha-response / g-recaptcha-responseCaptcha verification token, whichever your captcha provider's widget produces.

There is no field for choosing where a submission is delivered: destinations are read only from your own workspace configuration, never from the request. A public form key that could redirect delivery would turn the endpoint into an open relay.

The same goes for extra recipients: there is no _cc field. CC and BCC recipients are configured per email destination in the dashboard (paid plans). The notification template is likewise chosen per form in the dashboard, not per submission.

JSON submissions

Send content-type: application/json with any flat or nested object. Add accept: application/json to get a JSON response instead of HTML.

submit.js
await fetch("https://send.webforms.to/wft_your_key", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    accept: "application/json",
  },
  body: JSON.stringify({ email: "ada@example.com", message: "Hi" }),
});

A repeated key (a checkbox group posted as interest=a&interest=b) collapses into an array automatically, the same shape whether it arrived urlencoded, multipart or as a JSON array to begin with.

Responses

Successful JSON submissions return:

200 OK
{
  "ok": true,
  "alert": "success",
  "id": "sub_...",
  "message": "Thanks! Your message was received."
}

Every JSON response carries alert alongside ok (either "success" or "error"), so your own code can switch on one field instead of checking ok plus an HTTP status class. On Pro and Agency, a form can also merge its own flat string key/value pairs into the success and error responses (confirmation.customJsonSuccess and confirmation.customJsonError in the write API, or the form's Settings tab in the dashboard), useful for a constant like a client-side redirect flag or a support link, without needing your own server round trip to add it.

Validation failures return 422 with a list of field errors. Rate limiting returns 429 with a retry-after header; the ingest endpoint allows bursts and is limited per IP, per form. A submission that trips spam detection also returns 200 with a success message: bots that receive an error learn to adapt, so quarantine is silent by design. See the full error reference for every code the endpoint can return.

Delivery and retries

Deliveries fan out in parallel per destination. Transient failures retry with exponential backoff; permanent failures (bad webhook URL, revoked token) stop immediately and surface in your delivery log with the exact error. Email is the exception: each email is attempted once and never retried. A destination paused on a form is skipped for that form's submissions, including retries still queued when it was paused; other forms using it keep delivering. Every attempt records status, duration and response detail in the dashboard.

One bounce suppresses an address. When a recipient's server refuses an email, at send time or later, the address goes on a platform-wide suppression list and no form, auto-reply or account email is sent to it again. The email destination shows the reason. A whole domain is suppressed when the domain itself cannot receive mail, or when three of its addresses bounce within a week; shared providers such as Gmail are only ever blocked one address at a time. Soft bounces (a full mailbox) lapse after 24 hours.

Sending the same submission twice on a network retry is safe: pass an Idempotency-Key header and a repeat request with the same key returns the original result instead of creating a second submission.

Get your endpoint

Free for 300 submissions a month. No card required.

Start free