API Integration documentation

Submit a form

The endpoint that records a request, its headers and its responses.

POST /api/websites/{website}/forms/{slug}/submit

Records a request and triggers the client's notification email.

Check Value
Authentication website signature, X-Website-Signature and X-Website-Timestamp headers — or, from a server, Authorization: Bearer …
Origin the Origin header must match the website's host; not checked with a token
Captcha the website's chain — see The captcha; also with a token
Rate limiting api-submit, per IP and per website; 5 per minute on the default plan, plus 1 000 per hour per website
Body size 256 KB at most, 413 beyond — see Error codes

Path parameters

Name Type Purpose
website UUID The website the request comes from
slug string The form identifier — contact, quote

Headers

Header Required Value
Content-Type yes application/json
X-Website-Signature yes, without a token Signature obtained from the signature endpoint
X-Website-Timestamp yes, without a token Timestamp returned with that signature
X-Captcha-Provider depending on the chain Key of the chain link that produced the token
X-Captcha-Token depending on the chain The captcha token

Body

The form fields, by their field name, plus these context fields, all optional:

Field Type Purpose
page_url string URL of the page the form was submitted from; used to attribute the request to a page
page_title string Title of that page
page_locale string, 35 max Language of that page, from <html lang>en-GB
form_locale string, 10 max Language the form was rendered in — en; see Languages
visitor_hash string Fingerprint returned by the tracking endpoint; used for attribution
captcha_fallback string The fallback_token returned with a captcha_fallback error, when replaying a submission at the link the server designated — see The captcha

The script sends all of them, visitor_hash once it knows it. The language of the response — validation messages and confirmation message — is form_locale when the form offers it; otherwise page_locale (or the website language, when page_locale is missing or not a supported language) resolved among the form's languages, en-GB giving en; the form's source language as a last resort. The request records that language.

A field that is sent but not declared in the form is kept with the request: nothing is silently lost. A field hidden by a display condition is neither validated nor recorded. A consent field is sent as 1 when checked; a required one that is missing or not accepted is refused with a 422.

Example

curl -X POST https://api.example.com/api/websites/019f0000-0000-7000-8000-00000000000a/forms/contact/submit \
  -H 'Content-Type: application/json' \
  -H 'Origin: https://www.example.com' \
  -H 'X-Website-Signature: 7c1f…' \
  -H 'X-Website-Timestamp: 1789459200' \
  -H 'X-Captcha-Provider: recaptcha_v3:12' \
  -H 'X-Captcha-Token: 03AGdBq…' \
  -d '{
        "name": "Smith",
        "email": "[email protected]",
        "message": "Hello, I would like a quote for a swing gate.",
        "page_url": "https://www.example.com/contact",
        "page_title": "Contact",
        "page_locale": "en-GB",
        "form_locale": "en",
        "visitor_hash": "a509475f…"
      }'

Response 200

{
    "success": true,
    "message": "Your message has been sent. We will get back to you as soon as possible.",
    "lead_id": "019f0000-0000-7000-8000-0000000000ff"
}

message is the confirmation message defined by the client, in the language of the submission: display it as is. A message overridden per website only applies in that website's language; in any other language, the form's translated message is returned.

Errors

Code Body Cause
404 {"message": "Website not found."} Unknown UUID
404 {"message": "Form not found."} The form does not exist, is inactive, or is not linked to this website
403 {"error": "origin_not_allowed"} The Origin does not match the website's host
403 {"error": "invalid_signature"} Signature missing, tampered with or expired
403 {"error": "captcha_required"} No captcha token received although the chain requires one
403 {"error": "captcha_failed"} Token rejected by the provider, or longer than 4 096 characters
403 {"error": "captcha_provider_not_allowed"} The chain link is not one this submission may use — see The captcha
403 {"error": "captcha_fallback", "next": "altcha:7", "fallback_token": "1789459320.4f3b…"} The chain link is unavailable: start again with the link given, carrying fallback_token
413 {"error": "payload_too_large"} The body exceeds 256 KB
422 {"message": "…", "errors": {…}} Validation — see Validation errors
429 Too many submissions; see Rate limiting

From a server

A token from the website's organization replaces the signature and the origin check, not the captcha: when the website's chain has links, send the token solved on the page in X-Captcha-Provider and X-Captcha-Token, and the fallback_token of a previous captcha_fallback in the captcha_fallback field. The PHP library makes this call with submissions()->submit().

The client's quota never shows up here. A request received beyond their plan is accepted and recorded; only the notification email stops. A visitor never gets an error because of a billing matter.

Retrying properly

Two cases, and only two, deserve an automatic second attempt:

  • a 403 invalid_signature: request a new signature, then retry once. This happens when the website key was regenerated while the visitor was filling in the form;
  • a 403 captcha_fallback: resume solving at the next chain link, once per link, and send the fallback_token back in the captcha_fallback field of the replay. The token is valid for two minutes and only designates that one link; without it, the server refuses the link with captcha_provider_not_allowed unless it is an ALTCHA link.

Any other error is final: retrying the same request will produce the same response.