Formhook docs
Paste your API key into a form action, get submissions in your dashboard, get a system push notification for each new entry.
Quickstart
1. Sign up and verify your email.
2. Create a form in your dashboard. Copy the API key (looks like fh_…).
3. Paste it as the action of your HTML form. That's it.
HTML form (no JavaScript)
The simplest integration. Native form submission, optional redirect on success.
<form action="https://formhook.app/f/YOUR_API_KEY" method="POST">
<input name="email" type="email" required>
<textarea name="message" required></textarea>
<!-- honeypot: bots fill this, humans don't see it -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px">
<!-- where to send the user after success -->
<input type="hidden" name="_redirect" value="https://example.com/thanks">
<button type="submit">Send</button>
</form>JavaScript fetch
await fetch("https://formhook.app/f/YOUR_API_KEY", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "[email protected]",
message: "Hello",
}),
});React component
"use client";
import { useState } from "react";
export function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle");
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const data = Object.fromEntries(new FormData(e.currentTarget));
const res = await fetch("https://formhook.app/f/YOUR_API_KEY", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
setStatus(res.ok ? "ok" : "error");
}
if (status === "ok") return <p>Thanks — we'll be in touch.</p>;
return (
<form onSubmit={onSubmit}>
<input name="email" type="email" required />
<textarea name="message" required />
<button disabled={status === "sending"}>Send</button>
{status === "error" && <p>Something went wrong. Try again.</p>}
</form>
);
}cURL (testing)
curl -X POST https://formhook.app/f/YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","message":"hi"}'Reserved fields
These three field names are stripped from the stored payload:
_gotcha— honeypot. Anything non-empty here returns 200 success but silently drops the submission and doesn't consume your quota._redirect— URL to redirect to on success (form-encoded posts only). Must be on an origin in your form's allowlist; otherwise ignored.cf-turnstile-response— Turnstile token, validated server-side.
CORS
For browser submissions, add the origin (scheme + host, no path) to your form's Allowed origins in settings. An empty list rejects browser-origin requests entirely. Server-to-server requests (no Origin header) always go through, subject to rate limits.
Cloudflare Turnstile (optional)
Enable Turnstile in your form's settings, then embed the widget in your HTML:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async defer></script>
<form action="https://formhook.app/f/YOUR_API_KEY" method="POST">
<input name="email" type="email" required>
<div class="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITEKEY"></div>
<button type="submit">Send</button>
</form>Rate limits and quotas
- Per-IP across all forms: 10 requests/minute → 429 with
Retry-After. - Per-form: 60 requests/minute → 429.
- Body size cap: 64 KB → 413.
- Free tier monthly quota: 200 submissions over a rolling 30-day window. Over-quota still returns 200 in MVP with
warnings: ["over_quota"]in the body.
Error responses
| HTTP | code | meaning |
|---|---|---|
| 200 | ok | success |
| 302 | — | redirect to _redirect URL |
| 400 | invalid_body | malformed JSON / unsupported content-type |
| 403 | origin_not_allowed | CORS allowlist mismatch |
| 403 | turnstile_failed | Turnstile token missing/invalid |
| 403 | account_suspended | form owner is suspended |
| 404 | form_not_found | unknown api_key |
| 413 | body_too_large | body > 64 KB |
| 429 | rate_limited | includes Retry-After header |
| 500 | internal_error | try again, or report |
Push notifications (for you, the form owner)
Open your dashboard and click Enable notifications — your browser will ask for permission, then every new submission triggers a system notification, even when the tab is closed. On iOS, install Formhook to your home screen first (Safari 16.4+).