Skip to main content
The capture endpoint is a documented HTTP contract, not only something the allGood script talks to. Use this when you’re building the request yourself. For an ordinary web page, prefer the script — it handles the honeypot, the bot widget, field checks and double submissions for you.

The request

There is no API key and no authorization header here. The form id is a public identifier; the origin allowlist is the control.

The body

Send whatever your form collects. There’s no fixed schema at the endpoint — a field the form definition doesn’t know about is still stored rather than rejected. Three keys have special meaning: And one is ignored if you send it: consentTextId. allGood deletes it and substitutes the form’s own bound value from its own records, so the consent evidence can’t be forged from the page. Everything else is stored as sent.
_hp is only the tenant-wide default. Accounts rename it often, and a form can override it individually — the field actually in force for a given form is whatever its own install code uses. Don’t hardcode _hp; read it off this form’s own integration drawer (or its Test section’s Honeypot row) before you build the request.

Tracking fields

None of these are required or specially handled at the endpoint — they’re just the names the install script’s own hidden fields use when it reads them off the page’s URL. Send whatever subset your own tracking actually has: utmCampaign · utmContent · utmMedium · utmOffer · utmSource · utmTerm · utmPosition · utmDevice · utmType · utmGeo · gclid · adGroupId A server-side caller that wants parity with what the script would have sent from a real page should use these exact names rather than its own convention (utm_source, say) — otherwise the two paths report inconsistently in anything downstream that groups by UTM field.

Size limit

Your account setting, 8 KB by default, and never more than 16 KB whatever the setting says. Measured in bytes, not characters. Both the declared length and the real length are checked.

The order of checks

Checks stop at the first failure, so exactly one outcome describes a submission and it names the first thing that was wrong. Check 1 is the one to watch. A refused origin gets a bare 403 with no CORS headers, so a browser sees an opaque network error rather than a readable body. It cannot tell “not allowlisted” apart from “network down”. Use the runtime config endpoint to find out, or read the console warning the script writes. A form’s own Test section shows this same order resolved against its real configuration — which origins are actually allowlisted, whether a bot check is actually required, what its honeypot field is really named — rather than the generic version above.
The same checks, resolved to one tenant's and one form's actual configuration

The same checks, resolved to one tenant's and one form's actual configuration.

Preflight

A cross-origin JSON POST is never a simple request, so the browser always sends OPTIONS first. The origin is echoed exactly, never * — the path is credentialed, and * is invalid with credentials.

Responses

redirectUrl is present only when the form configures one and it passed the safety check. This endpoint never returns consent_missing. See Reason codes.

Retries

Accepted responses carry a submissionId, and everything downstream deduplicates on it. Retrying the same HTTP request mints a new one, so a retry does create a second record. Guard against double submission on your side, as the script does.

Rate limits

Event capture has separate budgets. A caller with no Origin header — a server — is covered by the per-IP budget only.

Posting from a server

You can. There’s no origin to check, so the allowlist doesn’t apply and the per-IP budget is the limit. Be aware that bot protection needs a browser-minted token, so a server-side caller is effectively unauthenticated — put your own rate limiting and validation in front of it.