FastLinkIt

Forms

Organizercontactsapimailing6 min read

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

  1. Open /forms/create (Professional / Unlimited / Admin only).
  2. 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.
  3. 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.
  4. Configure Notifications: notify yourself in-app + email on every submission, and / or send the submitter a branded confirmation email back. Both toggleable.
  5. 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:

  1. Persist the submission row + bump the form's submission counter.
  2. 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).
  3. Owner notification — in-app bell, optional email, optional browser push (gated by your form_submission preference on /Account/Manage).
  4. 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.
  5. form.submitted webhook — fires through the existing HMAC-signed bus, subscribers register at /payments/webhooks with form.submitted in 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.

  1. Define the target fields once at /contacts/custom-fields (text / number / date / boolean / url / email / select with options).
  2. 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.
  3. 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 / isNotSet filters 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 skills custom 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 Active on /forms/{id}/edit — visitors see a closed-state card.
  • Set Closes at to 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

  1. Create a form at /forms/create with short code contact-us. Fields: name, email, message. Toggle Send the submitter a confirmation email on.

  2. Drop the iframe widget on your blog's contact page:

    <script src="https://flnk.it/js/forms-widget.js" data-form="contact-us"></script>
    
  3. Build a drip sequence at /mailing/sequences/create with trigger tag_added + value source:form:contact-us. Steps: Day 0 thank-you, Day 2 case study, Day 7 "book a call" CTA.

  4. 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 /contacts tagged source:form:contact-us.

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.