How we tested
On 29 October 2026, between 14:12 and 14:26 UTC, we sent each of ten services the same requests with keys we made up, so nothing was stored or delivered. Every request carried a name, an email and a message. We posted them as JSON, as a form-encoded body and as multipart. We also sent the CORS preflight a browser sends before a JSON fetch, a plain HTML form post, and the same JSON from a server with no browser headers. The rules for reading each answer were written down before the first request. Requests went out with curl from a connection in Turkey. The browser check ran in Chromium on a page at example.com. We keep every raw answer and will send them to anyone who asks.
FormSubmit answered every request with Cloudflare error 521, which means its own server did not respond. We tried again 30 minutes later, and from a second network in Germany. Its home page returned 521 as well. That is an outage, not an answer about the form, so its row is empty.
JSON, form-encoded or multipart: what each endpoint read
| Service | Key goes in | JSON | Form-encoded | Multipart | Answer to Accept: application/json |
|---|---|---|---|---|---|
| Formgong | Body field | Read | Read | Read | JSON |
| Formspree | URL | Not refused | Not refused | Not refused | JSON |
| Web3Forms | Body field | Read (browser) | Blocked as a server call | Blocked as a server call | JSON |
| Forminit | URL | Not refused | Not refused | Not refused | JSON |
| Basin | URL | Not refused | Not refused | Not refused | JSON |
| FormSubmit | URL | No answer (521) | No answer (521) | No answer (521) | No answer |
| Formcarry | URL | Not refused | Not refused | Not refused | JSON |
| Formspark | URL | Not refused | Not refused | Not refused | Plain text |
| Static Forms | Body field | Read | Read | Read | JSON |
| Splitforms | Body field | Read | Read | Read | JSON |
No endpoint rejected any of the three formats, so you can post new FormData(form) or a JSON object to all of them. The difference is in what comes back. Formspark answered a JSON request with one line of plain text, so await res.json() throws on its error answers. Web3Forms treats any request without a real browser behind it as a server call, which is why curl got 403 for every format. From the browser it read the JSON body and named the bad key.
CORS: can a fetch from your own site read the answer
| Service | Preflight status | Access-Control-Allow-Origin | Allows content-type | Preflight cached for | Fetch from example.com |
|---|---|---|---|---|---|
| Formgong | 204 | * | Yes | 1 day | Readable |
| Formspree | 200 | Echoes the origin | Yes | Not sent | Readable |
| Web3Forms | 403 to curl | None to curl | None to curl | Not sent | Readable |
| Forminit | 200 | * | Yes | Not sent | Readable |
| Basin | 200 | * | Yes | 2 hours | Readable |
| FormSubmit | No answer (521) | Not tested | |||
| Formcarry | 204 | * | Yes | 20 days | Readable |
| Formspark | 204 | * | Yes | Not sent | Readable |
| Static Forms | 200 | * | Yes | 1 day | Readable |
| Splitforms | 204 | * | Yes | 1 day | Readable |
A fetch with a JSON body makes the browser ask the endpoint for permission first. If the endpoint says no, the browser never sends the post and your code gets only "Failed to fetch". All nine endpoints that answered gave that permission to any site. Web3Forms refused the preflight from curl but allowed it from the browser, the same server check as above. In our previous round, Jotform's submit address sent no CORS headers at all, which is why it is not on this list.
What a plain HTML form gets back
| Service | Status | What the visitor would see |
|---|---|---|
| Formgong | 303 redirect | An error page on formgong.com naming the problem |
| Formspree | 404 | An HTML page: form not found |
| Web3Forms | 403 (curl is treated as a server) | In round 2, from Chrome: an error page, invalid key |
| Forminit | 200 | An HTML page: form deleted |
| Basin | 200 | An HTML page: endpoint locked |
| FormSubmit | No answer (521) | |
| Formcarry | 200 | An HTML page: form is invalid |
| Formspark | 404 | One line of plain text: form does not exist |
| Static Forms | 401 | Plain text: Invalid API key |
| Splitforms | 404 | An HTML page: form setup issue |
A form with action and method="POST" and no JavaScript navigates to whatever the endpoint answers. Every service that answered showed some kind of error for a wrong key. Formgong is the only one here that redirects with 303 to an error page. The others answer with the page itself. Forminit, Basin and Formcarry send that error page with status 200. A visitor will not notice, but an uptime check or a test that only looks at the status will report a broken form as working.
Posting from a server
We sent the same JSON with no Origin, no Referer and curl's own user agent, the way a backend, a cron job or a serverless function would. Eight of the nine that answered gave the same answer as from the browser. Web3Forms answered 403: "Use our API in client side or contact support with server IP address (Pro plan is required)".
Some services discourage server posts even where they work. Formcarry's docs say to post from the browser because every visitor would share your server's IP against its limit of one post per 15 seconds. Forminit offers a separate protected mode with an API key for servers. Formgong takes server posts on every plan. The contact form API page has the rate limits we measured.
What to check before you pick one
- Posting from your server or a serverless function: avoid Web3Forms unless you are on its paid plan with an allow-listed IP. Mind Formcarry's per-IP limit.
- A JavaScript form that reads the answer: any of the nine works with CORS, but plan for Formspark's plain-text errors and Forminit's 200 status on errors.
- A plain HTML form with no JavaScript: all of them showed the visitor some kind of error for a broken key, so any of them will do.
- Uptime monitoring on the form: pick one that answers errors with an error status. Forminit, Basin and Formcarry answered a broken form with 200 to a plain post.
For free plans, limits and where each one stores data, see our test of 13 form backends.
What this test does not show
Made-up keys show how an endpoint fails, not how it delivers. We did not measure how fast mail arrives, spam filtering, or what a successful post returns, because that needs a real form on each service. Every answer here was seen once on 29 October 2026 from one place. Services change, and each one's own docs are the final word.
Tell us what to test next
Is a service missing, or did one of them answer you differently? Write to support@formgong.com and tell us what you saw. We read every message, and the next round of this test starts from what readers ask for.
Questions
What is a form endpoint service?
A hosted URL that accepts a form's POST, stores the message and sends it to you, usually by email. You put the URL in the form's action attribute or in a fetch call, and your site needs no server code of its own.
Does a form endpoint accept JSON?
All nine services that answered our 29 October 2026 test accepted a JSON body: Formgong, Formspree, Web3Forms, Forminit, Basin, Formcarry, Formspark, Static Forms and Splitforms. None refused form-encoded or multipart either. Formspark answered JSON requests with plain-text errors.
Can I post to a form endpoint from my server?
Eight of the nine that answered on 29 October 2026 treated a server post like a browser post. Web3Forms answered 403 and asks for a paid plan and an allow-listed server IP.
Why does my fetch to a form endpoint say Failed to fetch?
Usually the CORS preflight was refused, the endpoint is down, or an ad blocker stopped the request. All nine endpoints that answered our test allow cross-origin fetch, so check the browser console for the exact reason and the address for typos.
Sources
Checked on 29.10.2026 against these public pages: Formspree: system limits, Formspree: submit forms with JavaScript, Web3Forms API reference, Forminit: submit form API, Forminit llms.txt, Basin docs, FormSubmit documentation, Formcarry llms.txt, Formspark: AJAX, Static Forms, Splitforms docs, MDN: CORS preflight request, Formgong API reference (llms.txt).