Hosted form backend
Point any form at one URL. We handle the rest.
Share a hosted form, send personal links, or post from your own markup. quickforms validates every submission, filters spam, stores it, emails you and posts it to your webhook with a signature and retries. Test it all in sandbox mode, and drive it from the dashboard, the REST API or MCP.
- Hosted forms
- Personal links
- AI spam filter
- Signed webhooks
- REST API and MCP
How it works
Three steps from markup to inbox
No server to write for every site. Keep your own HTML, your own design and your own email provider.
- 01
Build your form
Describe it to the AI form builder, start from a template or write the schema yourself. Save changes as a draft and publish when they are ready.
{ "name": "email", "type": "email", "required": true } - 02
Share it or point at it
Share the hosted form page, send personal links, set your own form action to the endpoint, post JSON from your app, or drop in the embed script.
<form action="/f/FORM_ID" method="post"> - 03
Get it everywhere
Every submission is validated, checked for spam, stored, emailed to you and posted to your webhook with a signature. Failed deliveries retry on a backoff schedule.
Quickforms-Event: submission.created
Hosted forms
No website needed. Share the link.
Every form has its own page at /f/FORM_ID, styled the way you choose. Open it in a browser and it is a
form. Post to the same URL and it is an endpoint.
- Classic, compact, or steps: one question at a time, with keyboard shortcuts and a review before sending
- Light, dark or automatic, with your accent colour, font, corners, background, intro and logo
- File uploads, honeypots and Turnstile work on it out of the box
-
Preview a draft with
?sandbox=before you publish it
1→What's your name?
Prefilled from your link
"hosted": { "layout": "steps", "accent": "#7c3aed", "background": "grid" }
Personal links
One link per person. Know who answered.
Create a link for each customer, guest or applicant, one at a time or up to 500 at once. Each link knows who it is for, fills in what you already know and carries your own data through to the submission and its webhook.
- Single use by default, or set a limit, unlimited uses or an expiry
-
Used links say they have already been filled in. Custom forms get a clear
409 link_used metadatasuch as a customer ID rides along, unseen and unchangeable- Only accept submissions through links, and revoke any link at any time
- Create, list and revoke from the dashboard, the REST API or MCP
curl -X POST https://quickforms.test/api/v1/forms/FORM_ID/links \
-H "Authorization: Bearer $QUICKFORMS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"links": [
{ "label": "Ada Lovelace", "email": "ada@example.com",
"prefill": { "name": "Ada Lovelace" },
"metadata": { "customer_id": "cus_123" } }
]
}'Build and validate
Describe it, pick a template, or write the schema
The AI form builder turns a plain-English description into fields, smart rules, a success message and an autoresponder for you to review. Or start from a template. Either way, every field becomes a validation rule, so bad input never reaches your inbox or your webhook.
- text, textarea, email, url, tel, number, date, select, radio, checkbox, checkboxes, hidden and file
required,minandmaxfor length, value, count or date,pattern,options,acceptandmax_size_kb- Custom error messages per field, shown inline on the hosted page and in the embed
- Save changes as a draft, test them in sandbox mode, then publish or discard
{ "name": "email", "type": "email", "required": true }{ "name": "plan", "type": "select", "options": ["starter", "pro"] }{ "name": "seats", "type": "number", "integer": true, "min": 1, "max": 50 }Examples
Start from a form that already works
Real templates, ready to go: fields, validation, an autoresponder and smart rules for the spam filter. Pick one, change anything, then share the hosted link or point your own form at it.
Contact
Name, email and a message, with a confirmation email to the person who wrote in.
- 3 fields: Name, Email, Message
- Autoresponder: Thanks {{name}}, we got your message
Everything is editable after you pick it.
Contact
Features
Everything a form endpoint should do
The boring, important parts, done properly once so you do not have to do them again for every site.
Hosted forms
A themed page for every form in a classic, steps or compact layout, with light and dark modes, your accent, font and logo.
Personal links
One link per person with prefilled answers, your own metadata, a use limit and an expiry. Used links say so.
AI form builder
Describe the form in plain English and get fields, smart rules, a success message and an autoresponder to review.
Templates
Ready-made starting points for contact, quotes, newsletters, waitlists, feedback, RSVPs, job applications and more.
Drafts and publishing
Save changes as a draft and test them in sandbox mode while live submissions keep the published version.
Schema validation
Thirteen field types. Every field becomes a server-side rule, with 422 errors keyed by field.
AI spam filter
Every submission is classified before anything is sent. Spam is kept for review and its deliveries are held.
Smart rules
Plain-English rules per form, such as "Must be about bike fitting". Submissions that break them are held too.
Bot protection
Up to six honeypots with realistic names, and Cloudflare Turnstile with your own site key and secret key.
Double opt-in
Send a confirmation email first and hold every other email and webhook until the person confirms.
Direct uploads
Pre-signed URLs send files straight to storage, or post them as multipart. Type and size rules per field.
Sandbox mode
Validate and store test submissions without sending anything, and see exactly what would have gone out.
Signed webhooks
HMAC SHA-256 signatures with a timestamp, retries with backoff and a log of every attempt.
Email, your keys
Notify your team and reply to the sender through your own email provider, or the platform mailer.
HTML, JSON and embed
HTML posts get a redirect or a thank-you page, JSON clients get JSON, and one script tag renders the form.
CORS and rate limits
Allowed origins per form, safe redirects, and submissions limited per form and IP address.
Drop-in
Plain HTML, an embed, or one request
Use the markup you already have. Add the embed script to render the form for you. Or post JSON from any backend, script or serverless function.
_redirectsends people back to your site, or use the hosted thank-you page-
The embed renders every field type, posts with
fetchand firesquickforms:successandquickforms:errorevents - Prefer an iframe? Every form has a hosted embed page too
<form action="https://quickforms.test/f/FORM_ID" method="post">
<input name="name" required>
<input name="email" type="email" required>
<textarea name="message"></textarea>
<!-- Honeypot: hidden from people, bots fill it in -->
<input name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
<!-- Optional: send people here afterwards (the host must be trusted by the form) -->
<input type="hidden" name="_redirect" value="https://yoursite.com/thanks">
<button type="submit">Send</button>
</form>X-Quickforms-Sandbox: qf_sandbox_9vX2…- to
- you@yourdomain.com
- subject
- [Sandbox] New submission: Contact
- provider
- Postmark (Main)
Would have sent. Nothing left the app.
POST https://example.com/hooks Quickforms-Event: submission.created Quickforms-Signature: t=1762430400,v1=4b1e0c7d9a… { "sandbox": false, … }
Signed exactly as the receiver would have seen it.
{ "id": "01jca9qf3t…", "sandbox": false, "deliveries": null }Sandbox mode
Test for real. Send nothing.
Sandbox submissions are validated and stored like any other, but no email or webhook leaves the app. Instead you see what would have been sent, down to the webhook signature header.
-
Send the form sandbox key as
X-Quickforms-Sandboxor a_sandboxfield - Switch a whole form to sandbox mode while you build it
- Sandbox submissions run against your draft, so you can test changes before you publish
-
Preview the hosted page with
?sandbox=, and see the spam verdict on every test -
Set
QUICKFORMS_FORCE_SANDBOX=truefor a whole environment - Test submissions from the dashboard, API and MCP are always sandbox
Spam and bots
Four layers between bots and your inbox
Each layer catches what the one before it missed. Anything the spam filter flags is stored for review, and its emails and webhooks are held until you release it.
- 01
Rate limiting
Submissions are limited per form and IP address, with a clear 429 for anyone sending too many.
QUICKFORMS_RATE_LIMIT=30 - 02
Honeypots
A hidden main honeypot plus up to five extra ones with realistic names. Bots that fill them in get a normal success and are dropped.
"honeypot_fields": ["website", "fax"] - 03
Cloudflare Turnstile
Require a Turnstile token on live submissions, checked server-side with your own site key and secret key.
cf-turnstile-response - 04
AI spam filter
Every submission is classified before anything is sent, against a base spam check and your own plain-English smart rules.
"rules": ["Must be about bike fitting"]
Webhooks
Signed, retried and logged
Every webhook carries a timestamped HMAC SHA-256 signature, so you can prove it came from quickforms and reject replays. If your endpoint is down, quickforms retries on a backoff schedule and logs every attempt.
Quickforms-Signature: t=<unix>,v1=<hex>- Retries after 10s, 1m, 5m, 30m, 2h and 6h
- Status code, response excerpt, duration and next retry for each attempt
- Redeliver from the dashboard, the API or MCP
- Includes the personal link and your metadata when the submission came through one
- Held for spam until you release it, and for double opt-in until the person confirms
- URLs that resolve to private or reserved addresses are refused
import crypto from "node:crypto";
// Use the raw request body, exactly as received, not a re-serialised object.
export function verifyQuickforms(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
header.split(",").map((part) => part.trim().split("=", 2)),
);
const timestamp = Number(parts.t);
const signature = parts.v1 ?? "";
if (!timestamp || !signature) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return expected.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}| attempt | status | duration | response / next retry |
|---|---|---|---|
| #1 | 503 | 1840ms | next retry in 10s |
| #2 | err | 10000ms | connection timed out · next retry in 1m |
| #3 | 200 | 96ms | {"received":true} |
Bring your own keys
Email through the provider you already use
Add your own provider credentials per team, set a default, or pick one per form. Credentials are encrypted at rest and only ever shown masked. No provider set? The platform mailer sends it.
Notification email
Every field, to the addresses you choose or the team owner. Reply-to is the sender.
Autoresponder
Optional reply to the person who submitted, with {{name}} style placeholders.
API and MCP
Same powers in the dashboard, the API and your agent
Forms, templates, the AI form builder, drafts, personal links, submissions, spam review, deliveries and mail providers are all available over a REST API with team-scoped API keys. The same key connects an MCP client, so an agent can design a form, send a test submission, hand out personal links and read what would have been delivered.
Create API keys on the API keys page of your team.
curl -X POST https://quickforms.test/api/v1/forms/FORM_ID/test-submissions \
-H "Authorization: Bearer $QUICKFORMS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"data": {"name": "Ada Lovelace", "email": "ada@example.com"}}'Your next form needs a backend. This is it.
Describe a form, share its hosted page or copy the endpoint, and send your first sandbox submission.