Documentation

Getting Started

Collect form submissions from any website in minutes. No backend required — just an API endpoint and a few lines of code.

Paste into Claude, ChatGPT, or any AI assistant to get help integrating.

How it works

inForm gives you an API endpoint for each form you create. Your frontend submits data to that endpoint and inForm handles storage, spam filtering, contact management, and email notifications.

1

Create a form

Get a unique endpoint URL in the dashboard

2

Grab your API key

Authenticate submissions from your backend

3

Submit from your site

POST JSON via a server-side proxy

4

Manage & get notified

View submissions, configure email alerts


1

Create a form

Log in to your dashboard and navigate to Forms. Click Create Form and give it a name. The name is for your reference — it shows in notification emails and the dashboard.

Once created, you'll see the form's endpoint URL:

POST https://inform.synergistic.io/api/f/YOUR_FORM_ID
2

Get your API key

Go to Settings in the dashboard to find your API key. It starts with inf_ and authenticates every submission request.

Keep your API key secret. Never expose it in client-side code. Use a backend route or serverless function to proxy submissions.
3

Submit data

Send a POST request with your form data as JSON. Include the API key in the X-API-Key header. Since the key must stay secret, proxy through your own backend.

Backend proxy

app/api/contact/route.js
export async function POST(request) {
  const body = await request.json();

  const response = await fetch(
    "https://inform.synergistic.io/api/f/YOUR_FORM_ID",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-API-Key": process.env.INFORM_API_KEY,
        "X-Forwarded-For": request.headers.get("x-forwarded-for") || "",
        "User-Agent": request.headers.get("user-agent") || "",
        "Referer": request.headers.get("referer") || "",
      },
      body: JSON.stringify(body),
    }
  );

  if (!response.ok) {
    return Response.json({ error: "Submission failed" }, { status: 502 });
  }

  return Response.json({ ok: true });
}

Frontend form

ContactForm.jsx
function ContactForm() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(e.target));

    await fetch("/api/contact", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="message" />
      <input type="hidden" name="_hp" />
      <button type="submit">Send</button>
    </form>
  );
}
4

Spam protection

Every submission runs through built-in spam filtering automatically — no configuration needed. Spam is silently accepted (bots see a success response) but flagged in your dashboard. No notifications are sent for spam.

Honeypot

Add <input type="hidden" name="_hp" /> to your form. Bots fill hidden fields, humans don't.

Gibberish detection

Random character spam in name, company, and identity fields is caught automatically.

URL detection

URLs in name, title, and company fields are flagged.

Spam phrases

Common spam keywords and patterns are filtered.

Timing check

Set a hidden _loadedAt field to Date.now() on page load. Submissions under 3 seconds are flagged as bots.

IP rate limiting

Max 5 submissions per IP address per minute.

Bot user-agent blocking

Known automated tools (curl, wget, python-requests) are rejected.

5

Email notifications

Set up notification rules on each form to get emailed when submissions arrive.

Admin notifications

Send submission details to your team. Customize which fields appear, their order, and feature important fields with visual accents.

Submitter confirmations

Auto-reply to the person who submitted using their email or businessEmail field.

6

Field reference

inForm accepts any JSON structure — no predefined schema. Send whatever your form collects. These field names have special behavior:

FieldBehavior
email / businessEmailSubmitter confirmations & auto-creating contacts
name / firstName / fullNameAuto-populates the contact name
_hpHoneypot — stripped before storage
_loadedAtPage load timestamp for timing check — stripped before storage
_formIdOptional form variant identifier — stripped before storage
_turnstileTokenCloudflare Turnstile CAPTCHA token — required when Turnstile is enabled, stripped before storage
7

CORS & allowed origins

By default, submissions are accepted from any origin. To restrict access, add allowed origins in your form settings (e.g. https://yoursite.com). Only requests from listed origins will be accepted.

Since the API key should stay server-side, CORS is most relevant when proxying through your own backend.
8

Contacts

When a submission includes an email or businessEmail field, inForm automatically creates a contact record. Contacts aggregate all submissions from the same email address and can be organized with tags and notes in the dashboard.

9

CAPTCHA (Turnstile)

For stronger bot protection beyond the built-in heuristics, inForm supports Cloudflare Turnstile, a free, invisible, privacy-friendly CAPTCHA. It runs entirely on the client side — no puzzles for your users.

1. Get Turnstile keys

Go to dash.cloudflare.com and navigate to Turnstile. Create a widget and copy the Site Key (public) and Secret Key (private).

2. Add keys in inForm

Go to Settings in the dashboard and enter both keys in the CAPTCHA (Turnstile) card.

3. Enable per form

Open a form's settings and toggle Require CAPTCHA (Turnstile) on. The toggle only appears after Turnstile keys are configured.

4. Add the widget to your frontend

Load the Turnstile script and render the widget. Pass the resulting token as _turnstileToken in your submission body.

ContactForm.jsx
import { useEffect, useRef, useState } from "react";

function ContactForm() {
  const [token, setToken] = useState("");
  const widgetRef = useRef(null);

  useEffect(() => {
    // Load Turnstile script once
    if (!document.getElementById("cf-turnstile-script")) {
      const script = document.createElement("script");
      script.id = "cf-turnstile-script";
      script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js";
      script.async = true;
      document.head.appendChild(script);
    }
  }, []);

  async function handleSubmit(e) {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(e.target));
    data._turnstileToken = token;

    await fetch("/api/contact", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="message" />
      <input type="hidden" name="_hp" />
      <div
        ref={widgetRef}
        className="cf-turnstile"
        data-sitekey="YOUR_SITE_KEY"
        data-callback="(token) => setToken(token)"
      />
      <button type="submit" disabled={!token}>
        Send
      </button>
    </form>
  );
}
The _turnstileToken field is stripped from stored submission data automatically. If Turnstile is enabled and the token is missing or invalid, the submission returns a 403 error.