Skip to content

Forms

POST /_platform/forms takes a form a visitor sent on a site. In one step, Digize stores the submission, finds the CRM contact by the workspace’s duplicate rule (CRM settings) or adds one, adds Filled in the name form to their timeline, and starts every active automation whose Form submitted trigger matches the form.

A static site’s forms can post to /_platform/forms as they are. A site with its own Worker passes the visitor’s post on, with the visitor’s address:

if (url.pathname === "/_platform/forms" && request.method === "POST") {
return fetch(new URL("/_platform/forms", url), {
method: "POST",
headers: {
"content-type": request.headers.get("content-type") ?? "",
"x-forwarded-for": request.headers.get("cf-connecting-ip") ?? "",
referer: request.headers.get("referer") ?? "",
},
body: await request.arrayBuffer(),
redirect: "manual",
});
}

The first address in x-forwarded-for is the visitor’s, for the rate limit and Turnstile. The referer is kept as the page the form was on.

A urlencoded or multipart post is read field by field. A few names are the platform’s:

Field
_form (or form) The form’s name, such as contact or quote. Defaults to contact. Automations and the timeline go by it.
_redirect Where the browser goes next, on the site’s own hostname, such as /thanks.
cf-turnstile-response The Turnstile widget’s token, which it adds to the form. Required.
utm_source, utm_medium, utm_campaign, utm_term, utm_content Where the visitor came from: kept as the contact’s first touch, not as form fields.
gclid, gbraid, wbraid, fbclid, msclkid, ttclid, li_fat_id Ad click ids, kept the same way.

Any other name starting with _ is dropped, except the _gotcha honeypot, and so are files: forms take text only. Everything else is one of the form’s fields.

A post that goes through answers 303 to _redirect, or else back to the page it came from when that’s on the site, or else the site’s home page. One that doesn’t answers its error as JSON.

<form method="post" action="/_platform/forms">
<input type="hidden" name="_form" value="quote" />
<input type="hidden" name="_redirect" value="/thanks" />
<input name="name" placeholder="Your name" />
<input name="email" type="email" required />
<input name="phone" type="tel" />
<textarea name="details"></textarea>
<div class="cf-turnstile" data-sitekey="TURNSTILE_SITE_KEY"></div>
<button>Get a quote</button>
</form>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

With content-type: application/json, a form is either the same flat fields as an object, or this shape:

{
"form": "quote",
"fields": { "name": "Maya Chen", "email": "maya@example.com", "details": "Two bags a month" },
"turnstileToken": "0.ZXhhbXBsZQ…",
"pageUrl": "https://brightwave.yourfreesite.com/wholesale",
"attribution": { "utm_source": "newsletter" }
}

Values that aren’t strings are sent as their JSON text. A JSON post always answers JSON, never a redirect:

{ "submissionId": "fs_01JF8Q2M4N6P8R0T2V4X6Z8B0D" }
Field Becomes
email, phone The contact’s email and phone. At least one is needed, or the form answers 400 invalid with details.fields.email. A phone without a country code takes the CRM’s default country.
name Split into first and last name at the first space.
first_name or firstName, last_name or lastName First and last name, over name.
city The contact’s city.
tags Tags to add, separated by commas.
A custom field’s key Its value, for custom fields with Show on forms on (CRM › Custom fields).

The first 100 fields are kept on the submission, each value cut to 10,000 characters, and they’re in the automation’s trigger data with the submission’s id, the site, the hostname, the form’s name, the contact’s id, whether the contact is new, and the attribution.

A non-empty _gotcha field, in any of the formats, is a honeypot: the post is thanked and dropped, with nothing stored. Hide it from people (not with type="hidden", which bots skip) and leave it empty.

In this order, cheapest first:

  1. The body is at most 256 KB, counted as it streams in: 413 too_large.
  2. At most 10 posts a minute from one visitor to one site: 429 rate_limited.
  3. Turnstile’s token is checked with Cloudflare: 403 forbidden when it doesn’t pass or is missing.

Tokens come from Digize’s own Turnstile widget: put its site key in the form’s data-sitekey. GET /_platform/forms answers the key, and whether the widget runs on the hostname the request came to:

{ "turnstileSiteKey": "0x4AAAA…", "turnstileReady": true }

The SDK reads it with forms.settings().