Forms
Available on Professional and Unlimited
Build a form once, embed it anywhere, and turn every submission into a tagged contact ready for drip campaigns. The drag-and-drop designer covers the same field types as paid form-builder SaaS (text, email, dropdown, checkbox, rating, NPS, slider, matrix, image picker, signature). Three embed paths cover every host: hosted URL, iframe widget, or REST submit.
Build a form
- Open
/forms/create(Professional / Unlimited / Admin only). - Set Title, optional Description, and a unique Short code — the form will live at
flnk.it/form/{shortcode}. The short code is checked against every other short-code-bearing entity on the platform (links, pages, planners, campaigns, products, shops, aliases) so it's globally unique. - Drag fields onto the canvas in the Form fields section. Each field has a name you can set on the right-hand panel — that name becomes the JSON key for the answer.
- Configure Notifications: notify yourself in-app + email on every submission, and / or send the submitter a branded confirmation email back. Both toggleable.
- Save. The form is created active at
flnk.it/form/{shortcode}.
Where to embed
1. Hosted URL
Share flnk.it/form/{shortcode} directly — on social, in email, in a QR code, anywhere a URL can go. Works without any embed.
2. Iframe widget
One <script> tag mounts an iframe with the full Survey rendering on any third-party site:
<script src="https://flnk.it/js/forms-widget.js"
data-form="contact-us"
data-height="600"
data-mount="#form"></script>
Attributes:
| Attribute | Default | Description |
|---|---|---|
data-form |
required | The form's short code |
data-height |
600 | Iframe height in pixels |
data-width |
100% | Iframe width (any CSS value) |
data-mount |
(auto) | CSS selector for the mount point. Default: a <div> inserted right after the script tag |
data-api |
https://flnk.it | Override for self-hosted FastLinkIt deploys |
The iframe is sandboxed (allow-scripts allow-same-origin allow-forms allow-popups — no top-navigation, so the form can't navigate the host page).
3. REST submit
POST programmatically from any backend or workflow tool:
POST /api/forms/{shortcode}/submit
Content-Type: text/plain
{"email":"alice@example.com","comment":"hi"}
Anonymous, CORS-friendly (Content-Type: text/plain skips the preflight). Light per-IP rate limit of 5 seconds catches accidental double-clicks.
Both shapes accepted: a flat answers object (above) or an envelope {"answers":{...}}.
What happens on submission
Every submission triggers five side effects, in order:
- Persist the submission row + bump the form's submission counter.
- Auto-upsert contact — the submitter's email becomes a contact in your list with a
source:form:{shortcode}tag (Phase 1 source-tag convention). Existing contacts matched by email get empty fields filled in — never overwritten. Email + name are extracted from the answers JSON by looking for keys containing "email" / "name" / "fullname" (with email-regex fallback if no obvious key wins). - Owner notification — in-app bell, optional email, optional browser push (gated by your
form_submissionpreference on/Account/Manage). - Submitter confirmation email if enabled on the form — uses your custom subject + body if set, otherwise a sensible default that names you as the form owner.
form.submittedwebhook — fires through the existing HMAC-signed bus, subscribers register at/payments/webhookswithform.submittedin the events list.
Every step is best-effort isolated: a failure in any one (notification, email, webhook) won't roll back the persisted submission.
Map answers to contact custom fields
Forms capture email and name onto the contact automatically (see step 2 above). For richer questions — "Job title", "Company size", "Country" — map each form field to a contact custom field so the answer also lands on the contact record.
- Define the target fields once at
/contacts/custom-fields(text / number / date / boolean / url / email / select with options). - On the form editor, the Map fields to contact custom fields card lists every form field with a non-empty Name. Pick a target custom field per row, or leave on
— No mapping —to skip. - Save. On every submission the answer is written via the same upsert helper the contact editor uses — empty answers delete the row so segment
isSet/isNotSetfilters behave intuitively.
The mapping is gated by Collect submitter identity — turn that off and the entire mapping is skipped (the form stays a fully anonymous channel even if it has fields). On save the editor scrubs orphan keys (form fields you've since deleted) and dropped definitions (custom fields you've since deleted) so the persisted JSON stays small and accurate.
Use cases:
- A B2B lead form with "Job title" and "Company size" mapped to custom fields, then a smart segment like
Job title contains 'CEO' OR 'Founder'driving a tailored drip. - A volunteer signup form with "Skills" (multi-select checkbox) mapped to a
skillscustom field, then a segment for "Skills contains web design" when you need volunteers for a website refresh. - An event form with "Dietary requirements" mapped, queried later when sending venue numbers to the caterer.
Drip campaigns from form fills
The source:form:{shortcode} tag doubles as a drip trigger. Wire a sequence with:
- Trigger type:
tag_added - Trigger value:
source:form:{your-shortcode}
...and you have an end-to-end "form fills in → welcome series fires" pipeline with zero code. Step 1 ships at delay-0 (immediate); subsequent steps fire at the delays you configure.
Closing a form
Two ways:
- Toggle off
Activeon/forms/{id}/edit— visitors see a closed-state card. - Set
Closes atto a future datetime — the public URL returns the closed-state card automatically when that time passes. The REST endpoint returns HTTP 410 Gone.
Submissions already collected are kept regardless of closure.
Reading submissions
UI: /forms/{id}/stats shows the latest 200 submissions with expandable JSON answers, submitter email + name (when extracted), submitted-at timestamp, IP / country / referrer.
API: paginated read with the forms API key scope:
GET /api/forms/{id}/submissions?page=1&pageSize=50
X-Api-Key: fli_...
Returns the full answers parsed as a structured object (no double-parse needed by the consumer).
Webhook payload
Subscribe to form.submitted at /payments/webhooks. Payload shape:
{
"event": "form.submitted",
"timestamp": "2026-05-05T17:23:11.4567890Z",
"data": {
"formId": "3f...",
"formShortCode": "contact-us",
"formTitle": "Contact us",
"submissionId": 42,
"submitterEmail": "alice@example.com",
"submitterName": "Alice",
"countryCode": "GB",
"answersJson": "{\"email\":\"alice@example.com\",\"comment\":\"hi\"}"
}
}
Standard delivery contract: HMAC-signed (X-FastLinkIt-Signature: sha256=<hex>), fire-and-forget, auto-disabled after 10 consecutive failures.
Worked example — "Contact us" → 4-email welcome
Create a form at
/forms/createwith short codecontact-us. Fields:name,email,message. Toggle Send the submitter a confirmation email on.Drop the iframe widget on your blog's contact page:
<script src="https://flnk.it/js/forms-widget.js" data-form="contact-us"></script>Build a drip sequence at
/mailing/sequences/createwith triggertag_added+ valuesource:form:contact-us. Steps: Day 0 thank-you, Day 2 case study, Day 7 "book a call" CTA.Test by submitting through your blog. Within seconds:
- You get an in-app bell + email notification.
- The submitter gets the branded confirmation email.
- The Day 0 drip step fires.
- A new contact appears at
/contactstaggedsource:form:contact-us.
Related
- Drip campaigns — the triggers that fire from form tags
- Smart segments — segment by form-source tag for re-engagement
- Webhooks for contacts and mailing — same bus as
form.submitted