• Aug 30, 2026
  • 5 min read

Replacing a Formbricks Qualifier with My Own Form

My consulting site sells a paid 30-minute decision call. The rule in the terms is “qualifier first”: tell me the decision you’re facing before you book. For months that qualifier was a hosted Formbricks survey. Every call-to-action on the page — the hero button, two of the three offer cards, a “sign up for upcoming talks” link — opened Formbricks in a new tab.

It worked. But every other lead from my sites now goes through one central forms Worker into one inbox, with alerts, double opt-in and retention handled in one place. The qualifier was the exception: answers lived in Formbricks, and I’d have to sync them back to know who had asked what.

Own form vs Formbricks

Own form → central WorkerFormbricks
One listLands with every other lead: alert, weekly digest, backup, erasureSeparate dashboard; needs a webhook to join the list
PrivacyNo third-party script or processorA third-party script and processor to disclose
Without JavaScriptPlain <form method="post"> still worksDoesn’t
ConsentOptional updates box goes through the same double opt-inPossible, via mapping
LookExactly the site’s designTheir UI
Changing questionsA code change + deployEdit in a dashboard
AnalyticsCounts onlyPer-question drop-off

The last two rows are the honest case for Formbricks. For a qualifier with five fixed questions that I don’t plan to A/B test, they didn’t outweigh the first five.

Keep the visitor on the page

The biggest UX change wasn’t the form, it was the links. Every external Formbricks URL became an anchor:

  • “Qualify in 60 sec”, “See if you’re a fit”, the hero button → #qualify
  • “Sign up for upcoming” (talks) → #updates, a small newsletter box under the qualifier

The qualifier section sits directly above the booking calendar. Nobody leaves the site, and the next step is the next thing on screen.

The offer cards also pre-select the tier. Each card’s link carries a data-tier, and a few lines of script set the select before the jump:

<a href="#qualify" data-tier="Monthly Retainer" class="btn-ghost">See if you're a fit</a>
document.querySelectorAll<HTMLAnchorElement>("a[data-tier]").forEach((link) =>
  link.addEventListener("click", () => {
    const select = document.getElementById("q-tier") as HTMLSelectElement | null;
    if (select && link.dataset.tier) select.value = link.dataset.tier;
  }),
);

Without JavaScript the anchor still scrolls; the visitor just picks the tier themselves.

The form

Five questions plus contact details, all in the site’s own markup:

  • Which offer: decision call, monthly retainer, not sure yet
  • The decision or problem (required, free text)
  • Stage, budget bracket, timeline: three short selects
  • Name and role (optional), email (required)
  • An optional, unticked “also send me occasional updates” box
  • A honeypot field for dumb bots

It posts same-origin to /api/forms/submit with kind=qualifier. With JavaScript it sends JSON and shows the result inline; without it, the browser posts the form and the Worker redirects back with ?form=sent (or confirm, or error). On success the message is the funnel:

Thanks — got it. Next: book your call below ↓

What the Worker does with it

The Worker got a new qualifier kind for the consulting site’s registry entry: the free text is required, the five answers are stored as a small qualification object, and the alert on my phone summarises them without personal data — offer, budget and timeline, plus the email domain only. A question about “a vector DB for our RAG” from a CTO shows up as roughly “Decision Call, budget 1–5k, This month · from @startup.io”.

If the updates box is ticked, the lead goes through the same double opt-in with signed links as every newsletter: the form says “confirm via the email I just sent”, and nothing else is sent until they do. Unticked, they only ever hear from me about their question.

The policy had the better promise

The site’s existing privacy policy said: qualifier answers from people who don’t book are deleted within 90 days. My first version of the Worker kept qualifiers for 24 months, like enquiries.

I changed the code, not the promise: qualifier now expires after 90 days in KV (bookings are their own record and keep their own retention). Same lesson as when the privacy page found a retention bug: write the policy against the code, and when the policy is better, fix the code.

Nothing thrown away

The Formbricks setup — the Python script that created the surveys and the survey definitions as JSON — is still in the repo, untouched. I also tagged the last commit before the migration as pre-lead-hub, so the old version of the page is one git checkout away if I ever want the survey back.

I tested the new section in a browser against the built site with mocked Worker answers (plain, double opt-in, and the no-JS return), the same way as in testing form states with Playwright, then once for real from my own address: alert on the phone, confirmation email in the inbox.

Takeaways

  • One list beats a nicer survey UI when everything else already flows into one place.
  • Turn external CTAs into anchors; put the next step (the calendar) directly after the form.
  • Pre-select with data-* attributes; it degrades to a plain anchor.
  • Optional consent stays optional and unticked, and goes through double opt-in.
  • Alerts summarise, never quote: offer, budget, timeline, domain.
  • When the policy promises less retention than the code keeps, change the code.
  • Keep the old integration in the repo and tag the pre-change state.

Next: Cal.com bookings into your own pipeline with a signed webhook.