Documentation
Add a working form endpoint to any website in 60 seconds. Below: setup, integrations, the REST API, and answers.
Introduction
Formserve is a hosted form endpoint. You keep your HTML form on your own site, point its
action attribute at a unique Formserve URL, and submissions land in your dashboard,
your inbox, and any integrations you wire up — without you writing or hosting a backend.
It works with any static site (Astro, Next.js, Hugo, Eleventy, Gatsby), every CMS that lets you
edit HTML (Webflow, WordPress, Framer), every AI-generated website, and every framework that can
render a <form>. One URL is the entire integration.
New here? Skip to the Quick start below — you'll have a working form in under a minute.
Quick start
You'll go from zero to receiving a real form submission in under a minute. Pick a snippet for your stack:
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
await fetch('https://formserve.io/f/YOUR_KEY', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'maya@studio.io',
message: 'Loved your work — quote please?'
})
});
function ContactForm() {
return (
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<input type="email" name="email" required />
<textarea name="message" required />
<button>Send</button>
</form>
);
}
<template>
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<input type="email" name="email" required />
<textarea name="message" required />
<button>Send</button>
</form>
</template>
curl -X POST https://formserve.io/f/YOUR_KEY \
-H "Content-Type: application/json" \
-d '{"email":"maya@studio.io","message":"Hi"}'
Replace YOUR_KEY with the access key shown in your endpoint header or Customize area.
Three steps to live
- Sign up — free, no card.
- Create an endpoint — give it a name and your domain. Smart defaults wire up email notifications and spam protection for you.
- Paste the snippet above into your page and submit a test entry. It shows up in your dashboard and your inbox immediately.
Don't want to sign up just to try it? Use the test endpoint — create a temporary URL instantly, submit a prebuilt form, then claim it by email before the 24-hour or 10-submission limit.
Your first endpoint
An endpoint is a unique URL that receives submissions for one form on your site. You can create as many as your plan allows.
- Sign up for a free Formserve account — no credit card needed.
- Click New endpoint from your dashboard.
- Give it a descriptive name (e.g. Contact form).
- Add the domain(s) the form will live on (e.g.
example.com). For local testing,localhost:3000is pre-filled. - Save. Your endpoint URL appears on the Setup tab, ready to paste into your form's
actionattribute.
Smart defaults are applied automatically: email notifications go to your account email, spam protection is set to "medium," localhost is allowed for testing, and the email subject is auto-generated from the endpoint name. Tune any of it later in Customize.
The four endpoint tabs
- Submissions — inbox view, search, filter (Inbox / Favorites / All / Spam), export to CSV or JSON, mark favorites, and flag spam.
- Customize — access key, code snippets, allowed domains, spam protection, internal owner alerts, submitter auto-responder, file uploads, and redirects.
- AI Prompt — copy-paste prompts for Claude Code, Codex, Cursor, Lovable, Bolt, Replit, and other AI coding agents.
- Integrations — connect Slack, Discord, WhatsApp, Mailchimp, Google Sheets, Airtable, Notion, HubSpot, and outbound webhooks.
HTML forms
The default and simplest case. Point your form's action at your endpoint URL and use method="POST":
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<label>Name <input name="name" required></label>
<label>Email <input type="email" name="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<button type="submit">Send</button>
</form>
Replace YOUR_KEY with the access key shown in your endpoint header or Customize area. For a plain HTML form, the browser will go back to the submitting page when the referrer is available; if not, Formserve shows a hosted thank-you page. If you've configured a redirect URL, that takes precedence (see Redirects).
Field names become column names
Every name attribute on your form becomes a column in your Submissions dashboard, your email notifications, and your integration payloads. Use descriptive names: customer_email, not field_3.
AI prompt generator
If your site was built with Claude Code, Codex, Cursor, Lovable, Bolt, Replit, or another AI coding agent, open the endpoint's AI Prompt tab. Formserve creates a ready-to-paste prompt that tells the agent how to connect your existing form.
The prompt includes:
- Your public Formserve endpoint URL
- The required
method="POST"andactionattributes - Field naming guidance, including email validation
- Success and error message guidance
- Instructions to preserve the existing UI and styling
- Instructions not to create an unnecessary backend API route
Best use: paste the generated prompt into your AI coding tool from the root of the website project. Ask it to make the smallest change that connects the existing form to Formserve.
Cursor, Bolt, and v0 setup guides
Use the same endpoint URL everywhere. The agent-specific wording changes, but the integration contract stays simple: submit the existing form to Formserve and keep the current UI intact.
- Cursor: ask it to update the current form component, preserve the UI, add a hidden
_honeypotfield, and avoid creating a custom API route. - Bolt: ask it to wire the app form to your Formserve endpoint and test the success and error states after submission.
- v0: ask it to generate or update the form markup with
method="POST", your FormserveactionURL, email validation, and inline success handling if using Formserve.js.
Security: generated prompts only include the public endpoint URL. They do not include API keys, OAuth tokens, billing details, or private account data.
Formserve.js
Use Formserve.js when you want a polished client-side experience: the form submits asynchronously, the user stays on the same page, and the library shows a success message in place. It is the right default for JS-enabled sites that want to avoid a full-page redirect.
<form action="https://formserve.io/f/YOUR_KEY"
class="formserve"
data-formserve-endpoint="YOUR_KEY"
method="POST">
<input type="text" name="_honeypot" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px">
<label>Name <input name="name" required></label>
<label>Email <input type="email" name="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<button type="submit">Send</button>
</form>
<script src="https://formserve.io/v2/formserve.js" defer></script>
What it does: Formserve.js sends the form in the background, shows a loading state, and inserts a success message when the response does not include a redirect URL. If your endpoint has a redirect configured, the browser goes there instead.
Formserve.js identifies itself to Formserve, so a normal same-page fallback redirect is not returned to the library. That keeps the visitor on the same page and lets the inline success state show. Explicit redirect URLs configured in endpoint settings still take priority.
Existing sites using /v1/formserve.js continue to work with the original behavior. New installs
should use /v2/formserve.js for better inline feedback and error handling.
Response behavior
On a saved submission, Formserve.js receives JSON like this:
{
"status": "success",
"message": "Form submitted successfully",
"submission_id": "SUB-K7F9Q2M8",
"redirect_url": null
}
submission_id is the public submission reference shown in your dashboard and sent to
integrations. Use it in a custom thank-you message, support ticket, or client-side analytics event.
If redirect_url is present because the endpoint has an explicit redirect configured, the browser
navigates to it. Otherwise the library inserts an inline success message and resets the form.
For configuration or validation problems, the JSON contains "status": "error". Formserve.js treats that as an error even when the HTTP status is 200, so readable endpoint configuration feedback can still appear inline.
Customize success and error UI
Listen for browser events if you want to replace the built-in messages with your own UI:
const form = document.querySelector("form.formserve");
form.addEventListener("formserve:success", (event) => {
const result = event.detail.result;
console.log(result.submission_id); // "SUB-K7F9Q2M8"
});
form.addEventListener("formserve:error", (event) => {
console.log(event.detail.result.message);
});
If you need raw request control or want to wire your own UI, use plain fetch from your app — the endpoint still accepts application/json and standard HTML form posts.
React, Vue, and other frameworks
Because Formserve only needs a working action URL or a fetch call, every modern
framework works out of the box. Pick the snippet that matches your stack:
Every modern framework can render an HTML <form> — that's the whole integration story. Below are minimal idiomatic patterns for each stack:
Next.js (App Router)
// app/contact/page.tsx
export default function ContactPage() {
return (
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<input type="email" name="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
</form>
);
}
Astro
---
// src/pages/contact.astro
---
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
Vue / Nuxt
<template>
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<input type="email" name="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
</form>
</template>
Hugo / Eleventy / any static generator
Paste the raw HTML form from the Quick start above into any template. There is no framework-specific helper to install — the browser submits the form directly to Formserve.
Webflow, Framer, WordPress
Drop an HTML embed element on your page and paste the same HTML form. Webflow's native form widget can also be used — set the form's action URL in its element settings.
Single-page apps with custom UI
If you want to control the success/error UI yourself (no Formserve confirmation page, no redirect), POST JSON from your client:
const response = await fetch('https://formserve.io/f/YOUR_KEY', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const result = await response.json();
// { status: "success", message: "...", submission_id: "SUB-K7F9Q2M8", redirect_url: "..." }
if (result.status === 'success') {
showThankYou();
}
Redirects & success pages
By default, a plain HTML submission goes back to the page that hosted the form when the browser sends a referrer. If the browser does not send one, Formserve shows a hosted thank-you page. To force a custom destination, set the redirect URL in your endpoint settings, or include a hidden field in your form:
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<input type="hidden" name="_redirect" value="https://example.com/thanks">
<!-- ... -->
</form>
If you're using Formserve.js, the same-page fallback redirect is suppressed so the visitor stays on the page and sees the inline success state. Explicit endpoint redirect URLs still redirect.
Allowed domains
Allowed domains determine which websites can submit to your endpoint. This is Formserve's primary security control — endpoint keys are public (visible in your HTML), so the domain check is what stops abuse.
Forms submitted from a domain not in your allowed list are rejected and logged to the endpoint's spam log. Always include both your production domain and any preview / staging domains.
Format examples
example.com— allowsexample.comandwww.example.com*.example.com— allows every subdomainlocalhost:3000— for local development (include the port)example.com, *.myapp.com, localhost:3000— multiple domains, comma-separated
Spam protection
Every endpoint ships with invisible anti-spam layered together. CAPTCHA is optional and off by default; most forms never need it.
- Honeypot field — a hidden input bots fill out; humans don't.
- Rate limiting per IP — throttled per the endpoint's spam protection level.
- Keyword blocking — configurable blocked words.
- Email and IP blocklists — reject known spam sources.
- Weighted spam score — submissions with score ≥ threshold are flagged.
Set the level in your endpoint settings. The level only changes the IP rate limit; the other spam checks stay on:
- low — 60 submissions per IP per hour.
- medium — 30 submissions per IP per hour (default).
- high — 10 submissions per IP per hour.
Honeypots, allow/block lists, disposable-email scoring, and bot-signal scoring run regardless of the selected level.
Disposable email + bot signalsGrowth
Submissions whose email is on our disposable-domain blocklist, or whose request looks automated
(curl/wget/Python user agents, missing Accept-Language header) get extra weight
added to their spam score automatically. No configuration required.
For the strongest bot detection, add a hidden timestamp field that records when the form was loaded. We compare it to the submission time and flag obviously-automated fills (under ~1.5 seconds):
<form action="https://formserve.io/f/YOUR_KEY" method="POST">
<input type="hidden" name="_fs_loaded_at" id="_fs_loaded_at">
<!-- ...your real fields... -->
</form>
<script>
document.getElementById('_fs_loaded_at').value = Date.now();
</script>
The field is stripped from your stored submission data before saving — it's only used for scoring.
Email notifications
Email notifications are for internal submission alerts. New endpoints notify the endpoint owner by default, but you can turn owner alerts off without disabling email alerts for business recipients.
Use Notify endpoint owner when the developer or agency account owner should receive every submission. Use Send submission email to business recipients when the client, sales team, or business owner should receive the submission data. Business-recipient alerts work independently from owner alerts.
The subject supports template variables:
{{endpoint_name}},{{name}},{{email}},{{date}},{{time}}- Or any form field name you've submitted, e.g.
{{company}}
Auto-responder
Send a confirmation email back to the person who submitted the form — great for "we got your message, we'll reply within 24 hours" flows.
The auto-responder is available on Growth and Agency plans only. Free plans do not include submitter emails.
It requires the submission to include an email field named email, email_address, or user_email.
Growth lets you customize the subject and body. Agency adds branded controls like sender name, sender email, logo, action button, and footer.
File uploadsGrowth
Accept files (CVs, screenshots, project briefs, ID documents) alongside form data. Formserve stores uploaded files in S3 and keeps submission attachments private behind time-limited download links. Add enctype="multipart/form-data" to your form and a file input:
<form action="https://formserve.io/f/YOUR_KEY" method="POST" enctype="multipart/form-data">
<input type="text" name="name" required>
<input type="file" name="resume" accept=".pdf,.doc,.docx" required>
<button type="submit">Apply</button>
</form>
File uploads are a Growth-plan feature. Free-plan submissions silently drop attached files. Enable file uploads in Customize → File Uploads once you're on Growth or Agency. Production upload storage is S3-only.
Integrations
Once a submission lands, Formserve can fan it out to the rest of your stack. Email is on by default for every plan. The third-party integrations below are Growth-tier features.
Slack Growth
Post each new submission to a channel via a Slack Incoming Webhook. Per-endpoint configuration on the Integrations tab of the endpoint — paste a webhook URL from your Slack workspace and toggle it on. Create one in Slack →
Discord Growth
Post each new submission to a Discord channel via an incoming webhook. Per-endpoint configuration lives on the Integrations tab of the endpoint — paste the Discord webhook URL from your server and toggle it on. Create one in Discord →
WhatsApp Growth
Send every new submission to an internal WhatsApp number using the Meta WhatsApp Cloud API. On the endpoint's Integrations tab, add your WhatsApp Business phone number ID, access token, and recipient phone number. Formserve sends a plain-text summary with the endpoint name, public submission ID, timestamp, and key form fields.
Use a WhatsApp Cloud API number approved by Meta. The recipient phone should include the country code, for example +971501234567.
Mailchimp Growth
Sync submissions to a Mailchimp audience. Connect your Mailchimp account once at Integrations → Mailchimp (OAuth, no API key needed). Then on each endpoint's Integrations tab, map the endpoint to a list and pick which form field carries the email address.
Requirements: the submission must include an email field. By default Formserve looks at fields named
email, Email, EMAIL, email_address, or
user_email. You can override the mapping per-list.
Only one Mailchimp account can be connected per Formserve account. An endpoint can have multiple list mappings if you need to push the same form into different audiences.
Google Sheets Growth
Append every submission as a new row in a Google spreadsheet. Connect your Google account once at Integrations → Google Sheets, then pick a spreadsheet + tab per endpoint.
How rows are written: on the first submission, headers
timestamp, submission_id, and every form field name become row 1. Each
subsequent submission appends a row in that order. New fields discovered later are appended as new
columns — nothing is overwritten.
submission_id is the public Formserve reference for the entry, for example
SUB-K7F9Q2M8. You can use it in follow-up emails, support tickets, or internal
workflows without exposing Formserve's internal database ID.
Only one Google account can be connected per Formserve account. Each endpoint keeps one Google Sheets mapping at a time; edit the mapping to move the endpoint to a different spreadsheet or tab.
During connection, Google asks you to grant access so Formserve can list your spreadsheets and append rows to the sheet you choose.
Airtable Growth
Append every submission as a record in an Airtable base. Connect Airtable once at Integrations → Airtable, then on each endpoint pick a base + table.
Field mapping: we send each form field as a record field with the same name.
Make sure your Airtable column names match your form's name="…" attributes
— fields the table doesn't have are dropped silently. We also include Submission ID
and Submitted At if those columns exist on the table. Submission ID
contains the public reference, such as SUB-K7F9Q2M8.
Only one Airtable account can be connected per Formserve account. Each endpoint keeps one Airtable mapping at a time; update the mapping to switch bases or tables.
During connection, Airtable asks you to choose which workspaces or bases Formserve can access. Select the base that contains the table you want to write submissions into.
Notion Growth
Create a Notion database page for every new submission. Connect Notion once at Integrations → Notion, select the pages/databases Formserve may access in Notion's OAuth picker, then choose one database from each endpoint's Integrations tab.
Property mapping: Formserve matches form field names to Notion database property
names. Supported property types include title, rich text, email, phone, URL, number, checkbox,
date, select, and multi-select. If your database has a Submission ID property, it
receives the public Formserve reference. Every page also includes a submission summary in the page body.
Only one Notion workspace can be connected per Formserve account. Each endpoint keeps one Notion database mapping at a time.
During connection, Notion asks you to select which pages Formserve can access. Choose the page that contains the database you want to receive submissions.
HubSpot Growth
Create or update HubSpot contacts for every new submission. Connect HubSpot once at Integrations → HubSpot, then open an endpoint's Integrations tab and enable HubSpot sync for that endpoint. Formserve uses the submission email plus common contact fields like first name, last name, phone, and company.
Requirements: the submission must include an email field. Formserve looks for
email, Email, EMAIL, email_address,
user_email, or contact_email. Without an email, the HubSpot sync is skipped.
Only one HubSpot account can be connected per Formserve account. Each endpoint keeps one HubSpot mapping at a time.
HubSpot sync updates an existing contact when the email already exists, or creates a new contact when it does not.
Webhooks Growth
Per-endpoint outbound webhooks POST a JSON copy of every submission to a URL of your choice — great for Zapier, Make, n8n, or your own backend. Configure them per endpoint on the Integrations tab; each webhook gets its own signing secret, retries, and a delivery log.
Webhook URLs must use HTTPS and cannot point to localhost, private IP ranges, or URLs with embedded credentials. You can create multiple webhooks on one endpoint; the Integrations tab badge counts webhooks as one integration category.
Payload shape
Every delivery is a single POST with Content-Type: application/json:
{
"event": "submission.created",
"delivered_at": "2026-05-15T09:02:00Z",
"submission": {
"id": "SUB-K7F9Q2M8",
"created_at": "2026-05-15T09:01:58Z",
"spam_score": 0,
"flagged": false,
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 ..."
},
"endpoint": {
"id": "TZXI7zBLsaI",
"name": "Contact form"
},
"form_data": {
"name": "Maya Chen",
"email": "maya@studio.io",
"message": "Loved your work — quote please?"
},
"files": []
}
submission.id is the public submission reference shown in the dashboard and sent to
integrations. Endpoint owners can customize the prefix in endpoint settings.
Request headers
X-Formserve-Event— e.g.submission.createdortest.X-Formserve-Webhook-Id— the numeric ID of the webhook in your account.X-Formserve-Timestamp— UNIX seconds of when we sent the request.X-Formserve-Signature— HMAC-SHA256 hex digest of the raw request body, using your webhook's signing secret as the key.- Any custom headers you configured on the webhook (sent verbatim).
Verifying the signature
Always verify X-Formserve-Signature in production. Compare the HMAC-SHA256 of the raw body against the header, in constant time:
// Node.js (Express)
const crypto = require('crypto');
const secret = process.env.FORMSERVE_WEBHOOK_SECRET; // "whsec_..."
app.post('/hooks/formserve', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
const received = req.get('X-Formserve-Signature') || '';
const ok = crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(received, 'hex'));
if (!ok) return res.status(401).send('bad signature');
const payload = JSON.parse(req.body.toString());
// ... process payload ...
res.sendStatus(204);
});
# Ruby (Rack / Sinatra / Rails)
expected = OpenSSL::HMAC.hexdigest("sha256", ENV["FORMSERVE_WEBHOOK_SECRET"], request.raw_post)
received = request.headers["X-Formserve-Signature"].to_s
return head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(expected, received)
# Python (Flask)
import hmac, hashlib, os
expected = hmac.new(os.environ["FORMSERVE_WEBHOOK_SECRET"].encode(),
request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Formserve-Signature", "")):
abort(401)
Retries and delivery log
If your server returns anything other than a 2xx, we retry up to two more times with polynomial backoff. Every attempt — success or failure — is logged on the endpoint's Webhook deliveries page, with the request body, HTTP response code, and a replay button for failed attempts.
Zapier & Make
Use Zapier's Webhooks by Zapier → Catch Hook trigger, or Make's Webhooks → Custom webhook. Paste the URL they give you into a Formserve webhook and you're done — both services parse the JSON automatically.
Viewing & filtering submissions
The Submissions tab on each endpoint shows a table view of everything received, with:
- Search across form data, sender details, and metadata
- Filter by Inbox, Favorites, All, or Spam
- Favorite important submissions or toggle spam status without leaving the inbox
- Inspect delivery status and timestamps for email, Slack, Discord, WhatsApp, webhooks, and third-party syncs
Exporting submissions
From the Submissions tab, export to CSV or JSON. Use the export filters to scope by date range, exclude spam, or pick specific fields. Large exports are prepared in the background and exposed through a temporary download page.
Business inboxAgency
The business inbox is a simplified view for clients or business owners who only need to read and respond to leads. It hides endpoint setup, delivery logs, API details, webhook retries, and other developer tools.
Business-owner users see the same customer messages, attachments, read state, and favorites, but in a cleaner inbox built around the person who contacted the business rather than the technical delivery path.
- Use it for: founders, sales teams, support teams, and client stakeholders.
- Do not use it for: developers who need endpoint settings, integrations, exports, or delivery diagnostics.
- Access: add the person from the Team page with the Business owner role and select the project inboxes they can open.
Retention & deletion
Each plan has a retention window for submissions. Older submissions are deleted automatically by a daily cleanup job:
- Free — 7 days
- Growth — 90 days
- Agency — 365 days
You'll receive a heads-up email at least 7 days before deletion, listing each endpoint and how many submissions are about to expire, with a one-click upgrade option if you want to keep them.
Team accessAgency
Agency accounts can add teammates from the Team page. Access is granted by project: enter the teammate's email, choose their role, then select the project inboxes they can open.
Team members only see submissions, files, settings, and delivery history for endpoints inside projects explicitly shared with them. Adding someone to the team does not expose every project in the account.
Assign project access
- Open Team from the dashboard sidebar.
- Enter the teammate's email address.
- Choose a role: Viewer, Business owner, Editor, or Admin.
- Select only the projects this teammate should access, then save.
If the email already belongs to a Formserve user, they are added immediately and receive an access email with a link to their inbox. If the email is not registered yet, Formserve sends an invitation email with an accept link.
Roles
- Viewer — can view selected endpoints, submissions, and file attachments.
- Business owner — Viewer permissions plus clean customer inbox actions, without technical setup, exports, API keys, integrations, or delivery diagnostics.
- Editor — Viewer permissions plus selected endpoint settings, exports, test submissions, spam/read/favorite actions, and deleting submissions.
- Admin — Editor permissions plus duplicating and deleting selected endpoints.
Admin is powerful: admins can delete endpoints and submissions. Only assign this role to people trusted to manage production forms.
What team members cannot do
- Manage billing or subscriptions
- Invite, remove, or change roles for other teammates
- Access API keys
- Connect or manage OAuth integrations
- Create endpoints under the owner account
Endpoint creation still follows the signed-in user's own plan limits. For example, a free-plan teammate invited to an Agency account can help manage shared Agency projects, but cannot create new endpoints inside that Agency account.
How shared access appears
Endpoints available through a shared project are marked with a Team access badge and the teammate's role. This appears on the endpoint list, endpoint header, dashboard endpoint cards, and inbox views so teammates can distinguish their own endpoints from endpoints assigned by an Agency account.
The global Inbox shows submissions from every endpoint the user can access. Teammates can use the endpoint dropdown to filter to a specific assigned endpoint.
Client workspacesAgency
Client workspaces help Agency accounts organize endpoints by client, project, brand, or campaign. They are useful when one team manages forms for multiple websites and needs a cleaner way to filter endpoint lists without creating separate Formserve accounts.
Create a workspace
- Open Workspaces from the dashboard sidebar.
- Create a workspace with a client or project name.
- Add optional internal notes if they help your team identify the workspace.
Add endpoints to a workspace
You can assign an endpoint when creating it, or move an existing endpoint later:
- Open the endpoint.
- Go to Customize.
- Choose a workspace from the Workspace dropdown.
- Save changes.
The endpoint list can be filtered by workspace, including an Ungrouped view for endpoints that have not been assigned yet.
Workspace permissions: team member access is granted to selected workspaces/projects. A teammate can open the endpoints inside the projects you share with them, but they cannot see other projects in your account.
Deleting a workspace
Deleting a workspace does not delete endpoints or submissions. Any endpoints in that workspace are moved back to Ungrouped.
Channel setup and diagnosticsMulti-channel
Formserve can store inbound leads from more than web forms. The current channel foundation supports web form, WhatsApp, and email sources in the same inbox model.
WhatsApp inbound messages
Configure Meta WhatsApp Cloud API to call /webhooks/whatsapp. The verification token is configured
server-side with WHATSAPP_WEBHOOK_VERIFY_TOKEN, and signed callbacks are validated with
WHATSAPP_APP_SECRET. Match an endpoint by setting its WhatsApp phone number ID in endpoint notification settings.
Text messages create or update an inbox conversation grouped by the sender's WhatsApp ID. Unsupported message types are stored as ignored webhook events with diagnostics so they can be reviewed without appearing as valid leads.
Inbound email
Provider-normalized inbound email JSON can be posted to /webhooks/inbound_email with a bearer token
configured as INBOUND_EMAIL_WEBHOOK_TOKEN. Route messages to an endpoint by using the endpoint key
as the local part, for example YOUR_ENDPOINT_KEY@inbound.formserve.io.
Email threading uses in_reply_to, references, or message_id when available.
If those headers are missing, Formserve falls back conservatively to sender, recipient, and normalized subject.
Attachment metadata is stored in the existing submission file-data structure.
Troubleshooting
Admin users can review channel webhook diagnostics in the admin area. Failed events include safe metadata and error messages, while business-owner users only see valid inbox messages.
REST API
The Formserve API gives you programmatic access to your endpoints and submissions —
useful for backfilling data into another system, building custom dashboards, or automating
endpoint creation. All paths are under https://formserve.io/api/v1/ and return JSON.
Authentication
Every API request requires an API key. Generate one in your dashboard under Settings → API Keys. Each key inherits the permissions of the user who created it.
Pass the key in the X-API-Key header (preferred):
curl https://formserve.io/api/v1/endpoints \
-H "X-API-Key: fs_live_abcd1234…"
Or as a query parameter (useful for quick browser testing — avoid in production):
curl "https://formserve.io/api/v1/endpoints?api_key=fs_live_abcd1234…"
API keys grant full access to your account's endpoints and submissions. Treat them like passwords — store in environment variables, rotate if exposed, and revoke unused keys.
Endpoints
List your endpoints
GET /api/v1/endpoints
Retrieve a single endpoint
GET /api/v1/endpoints/:id
Create an endpoint
POST /api/v1/endpoints
Content-Type: application/json
X-API-Key: …
{
"endpoint": {
"name": "Newsletter signup",
"allowed_domains": "example.com, *.example.com"
}
}
Update or delete an endpoint
PATCH /api/v1/endpoints/:id
DELETE /api/v1/endpoints/:id
Submissions
List submissions for an endpoint
GET /api/v1/endpoints/:endpoint_id/submissions
Retrieve a single submission
GET /api/v1/endpoints/:endpoint_id/submissions/:id
Delete a submission
DELETE /api/v1/endpoints/:endpoint_id/submissions/:id
Submitting form data (the public endpoint)
The public form-submission URL is not part of the authenticated REST API. It's the URL you put in
your form's action attribute. It accepts both standard form encoding and JSON, and
needs no key — the allowed-domains check on the endpoint is the security control.
POST /f/:access_key
Content-Type: application/json # or application/x-www-form-urlencoded
{ "email": "maya@studio.io", "message": "Hi" }
Response on success:
{
"status": "success",
"message": "Form submitted successfully",
"submission_id": "SUB-K7F9Q2M8",
"redirect_url": "https://your-site.com/thanks"
}
submission_id is the public reference for the saved entry. You can show it in a
thank-you message, attach it to a support workflow, or use it to find the submission in the
dashboard. Endpoint owners can customize the prefix in endpoint settings.
Errors
The API uses standard HTTP status codes:
200 OK— success201 Created— resource created400 Bad Request— malformed payload401 Unauthorized— missing or invalid API key403 Forbidden— the API key is valid but doesn't have access to the resource404 Not Found— resource doesn't exist or isn't visible to your key422 Unprocessable Entity— validation failed (e.g. missing required field)429 Too Many Requests— rate limit hit500 Internal Server Error— transient issue; retry with backoff
Error responses include a JSON body with details:
{
"status": "error",
"message": "Endpoint not found",
"errors": ["Endpoint with that ID doesn't exist"]
}
Troubleshooting
My submissions aren't arriving
- Check the endpoint's spam log. If your form domain isn't in the allowed list, submissions are silently rejected and logged here, not in the inbox.
- Verify the access key. The URL in your form's
actionmust match the key shown in the endpoint header or Customize area exactly. - Confirm email notifications are enabled. Customize → Notifications.
- Check your monthly submission limit. If you've hit the cap on your plan, submissions are throttled (not lost) — visit Billing to upgrade.
The form posts but I end up on the thank-you page
That is expected when the browser does not send a referrer and you have not configured a redirect URL. If you want the browser to return to the submitting page, make sure the page allows a referrer. If you want a fixed destination, configure a redirect URL in your endpoint settings or add <input type="hidden" name="_redirect" value="…"> to your form.
Slack / Discord / Mailchimp / Google Sheets / Airtable / Notion / HubSpot / Webhooks isn't syncing
- These are Growth-plan features. Confirm you're on Growth or Agency.
- For OAuth integrations (Mailchimp, Google Sheets, Airtable, Notion, HubSpot), the connection may have expired — reconnect from the integration's settings page.
- Check the integration's per-mapping error message on the endpoint's Integrations tab.
- For webhooks, inspect the endpoint's Webhook deliveries page for request bodies, response codes, retry attempts, and replay actions.
Need help?
Email support@formserve.io with your endpoint key and a description of what's happening — we read everything personally.