Post-Conversion Triggers¶
Overview¶
Post-conversion qualification is the flow that opens the Rose widget after a visitor submits a client form (e.g., demo request, free trial signup), asking additional profiling questions to enrich the lead before it hits the CRM.
There are two activation modes, configurable per form via qualification.profiling.forms.<formId>.trigger:
- Thank-you page (
trigger.pages) — the widget activates when the visitor lands on a configured thank-you URL after submitting the client form. - Inline form-submit (
trigger.on_form_submit) — the widget activates on the same page the form lives on, when the client form submits but does not redirect (inline AJAX confirmations like Unbounce, HubSpot embeds, etc.).
Both modes can be configured simultaneously; whichever condition fires first activates the form.
IX-2646
The inline form-submit path was introduced for landing pages that show an in-place confirmation instead of redirecting to a thank-you URL. See the Linear ticket for the original use case (Matera).
When to Use Each Mode¶
| Client behaviour after submit | Mode |
|---|---|
| Redirects to a dedicated thank-you page | trigger.pages |
| Shows an inline success message, stays on the same URL | trigger.on_form_submit |
| Mixed across pages | Both — each page falls through to whichever matches |
Mode 1 — Thank-You Page (trigger.pages)¶
The default post-conversion path. When the visitor lands on a URL matching one of the configured patterns, SiteConfigManager.getActivePostConversionForm returns the form and the widget auto-opens into qualification.
pagesuses the same wildcard syntax as the widget'sdisplay_only_on_urls(*matches any substring).- No event wiring required — the widget's existing URL-change listener re-evaluates the display decision on navigation.
Mode 2 — Inline Form-Submit (trigger.on_form_submit)¶
When the client form doesn't redirect, Rose listens for the form submission event, persists an activation flag, and dispatches a custom window event so the widget's display controller switches into post-conversion mode on the same page.
{
"trigger": {
"type": "post_conversion",
"pages": [],
"on_form_submit": {
"enabled": true,
"pages": ["/landing/demo*"],
"form_action_matches": ["https://get.example.com/fsg"]
}
}
}
Three safety gates¶
All three must pass before the qualification form activates:
enabled: true— explicit opt-in per form.pagesmatch — current URL matches a configured landing-page pattern.form_action_matchesmatch — the submitted form'sactionattribute matches a configured URL pattern.
The third gate is non-optional: an empty form_action_matches blocks activation even when enabled: true. This is a safety gate — without a per-client form-action contract, any stray submission on the page (newsletter signup, search box, cookie banner) would trigger qualification.
How to fill on_form_submit¶
- Open the landing page in DevTools → Network tab → submit the form with test data.
- Look at the POST request's URL. That's the form action URL — paste it (or a pattern with
*) intoform_action_matches. - Add the landing page path to
pages(the URL where the form lives, not the thank-you URL — there isn't one). - Set
enabled: true.
How it works end-to-end¶
Key invariants¶
prevDecision.allowedmust already betrue. The form-submit event only flips an already-displayed widget into post-conversion mode. It does not force the widget to appear on a page where display rules would otherwise hide it. If you configureon_form_submiton a page that isn't indisplay_only_on_urls/page_display_rules, you'll see a⚠️ DisplayController: form-submit activation ignoredwarning in the console — add the page to the widget's display rules.- Dispatch is gated on a successful
sessionStorage.setItem. If storage is blocked (privacy extensions, ITP in iframes, quota exceeded), the warning is logged and the event is not dispatched, avoiding a silent no-op that would falsely report "activation triggered". - Activation is session-scoped (until tab close).
isFormSubmitActivatedreads fromsessionStorage, so re-opens on revisits within the same tab are intentional — the visitor is nudged back to finish qualification. Two higher-level gates inRoseWidgetprevent unwanted re-prompts:completedPostConversion(localStorage) — skip auto-open if qualification already finished.rose_pcq_dismissed_{domain}(sessionStorage) — skip auto-open if the visitor explicitly dismissed the qualification.
How to verify in production¶
- Submit the form on the landing page with test data.
- Rose widget should open into the qualification form within ~1 second, on the same page.
- In PostHog, confirm
rw_client_form_submittedfires, followed byrw_widget_impressionwithisPostConversion=truein the same session.
Configuration Reference¶
The JSON schema lives at schemas/configs/website/qualification.schema.json. Generated types:
- TypeScript —
frontend/shared/src/types/configs.generated.ts(QualificationProfilingFormsValueTriggerOnFormSubmit) - Python —
backend/packages/ixconfig/ixconfig/models/generated.py
Related Components¶
| Layer | File |
|---|---|
| Form submission listener | frontend/shared/src/config/PostConversionFormSubmitTracker.ts |
| Display re-evaluation | frontend/widget/src/components/DisplayController.tsx |
| Active form resolution | frontend/shared/src/config/SiteConfigManager.ts (getActivePostConversionForm) |
| Analytics hookup | frontend/shared/src/analytics/index.ts (onFormSubmit → notifyFormSubmit) |
| Widget auto-open gates | frontend/shared/src/components/RoseWidget.tsx |
Related Docs¶
- Qualification Flow & CTA Blocking — pre-conversion qualification behaviour during chat.
- Prospect Qualification (client-facing) — webhook payloads emitted on
post_conversion_complete. - Rose Config —
rose-configskill — operational commands to editqualification.profiling.forms.*.triggerin each environment.