Conversion Attribution¶
Goal¶
Rose needs two things to attribute a conversion to a conversation:
- Give the session ID to the client — so the client's CRM knows which Rose conversation led to this conversion
- Get the conversion back — so Rose can mark the session as converted
On Axis 2, there are two levels of data:
- Event only: Rose knows that a conversion happened, but not what was submitted
- With form data: Rose gets the full conversion data (email, company, form fields, etc.)
The Two Axes¶
| Axis 1: Give the ID to the client | Axis 2: Get the conversion back | |
|---|---|---|
| Automatic | UTM on CTA links, booking link decoration, cookies | Event only: PostMessage, form detection, thank-you page |
| Automatic (discovery) | — | With form data: POST request capture |
| Client action | Client reads cookie/UTM into hidden field | With form data: HubSpot OAuth, or webhook to Rose |
Decision Tree¶
The tree has two parts matching the two axes. First: how does the session ID reach the client? Then: how does the conversion reach Rose?
Axis 1: How does the session ID reach the client?¶
The visitor doesn't need to click a Rose CTA for conversion attribution to work. If the visitor chatted with Rose and then clicked the client's own "Book a demo" button, the session ID is still available in the rose_last_active_session cookie. Rose also decorates all Calendly/Cal.com links on the page regardless of whether they come from a Rose CTA.
Axis 2: How does the conversion reach Rose?¶
These paths work whether the visitor clicked a Rose CTA or the client's own button — Rose watches the page regardless.
"Conversion happens on the page" covers all of these — Rose detects them for all visitors, whether they interacted with Rose or not:
| What | How Rose detects it |
|---|---|
| Calendly popup or inline embed | PostMessage (calendly.event_scheduled) |
| Cal.com embed | PostMessage (bookingSuccessfulV2) |
| HubSpot iframe form | PostMessage (onFormSubmitted) |
| Typeform embed | PostMessage (form-submit) |
| HubSpot embedded React form | DOM observation (form replaced by success message) |
| Superform (Webflow) | sf:complete custom event |
Standard HTML <form> |
Native submit event |
SPA form (no <form> element) |
Network observer intercepts fetch/XHR |
Legend:
| Color | What Rose gets | Works for all visitors? |
|---|---|---|
| 🟢 Green | Form fields (email, company, etc.) | Yes — automatic for all visitors |
| 🔵 Blue | Conversion event only (knows that it happened, not who) | Yes — automatic for all visitors |
| 🟡 Yellow | Depends on client setup — only for visitors who interacted with Rose | No — requires session ID (Axis 1) + client configuration |
| 🔴 Red | Nothing | — |
Axis 2 Detail: Event only vs. with form data¶
Event only (conversion event, no field values)¶
Rose detects that a form was submitted or a booking was completed, but does not get the submitted field values.
| Method | How it works | What Rose gets |
|---|---|---|
| PostMessage (Calendly, HubSpot iframe, Cal.com, Typeform) | Provider iframe sends a PostMessage event on completion | Event type, provider name, session ID |
| HTML form detection (CTAPageFormStrategy) | Rose listens for native submit event |
Form ID, page URL, success/failure status |
| Superform detection | Rose listens for sf:complete event |
Form ID, target step reached |
| Network observation | Rose intercepts outbound POST after button click | Request URL, content type (payload redacted) |
| Thank-you page redirect | Rose detects URL matching configured thankyou_page_patterns (initial load + SPA navigation) |
Matched URL pattern, session ID from cookie |
This is enough for: - Interest scoring (visitor reached conversion stage → score 5) - Attribution dashboards ("X sessions led to a form submission") - CTA click tracking
This is NOT enough for: - Feeding the client's CRM with lead data - Qualification / lead scoring with field values - Knowing who converted (email, company)
With form data (full conversion data)¶
Rose gets the actual form field values — email, company, answers to qualification questions, etc.
| Method | How it works | What Rose gets | Status |
|---|---|---|---|
| POST request capture | Rose intercepts fetch/XHR/form POST on configured pages and captures the payload | Full form payload (keys + values, or redacted values) | Done (discovery mode) |
| HubSpot sync | Client connects OAuth, Rose periodically syncs contacts | Contact fields, company, deal data | Partial |
| Client webhook → Rose | Client POSTs conversion event with form fields to Rose endpoint | Email, metadata, session ID | Needs building |
| Rose post-conversion form | Visitor answers Rose's own qualification questions in the chat | All collected fields + AI scoring | Done |
Path details¶
Calendly / Cal.com¶
Rose automatically decorates all Calendly and Cal.com links on the client's website with the session ID as a UTM parameter. The client's only job is to configure their booking tool to capture that UTM value.
What Rose does:
1. Sets session_utm_name in the client's config during onboarding (e.g., utm_id)
2. Detects all booking <a> links on the page and appends ?{utm_param}={session_id}
3. If the booking form is embedded as an iframe, also detects completion via PostMessage
What the client does:
| Step | Action | Effort |
|---|---|---|
| 1 | Configure a hidden field in Calendly/Cal.com to capture the UTM parameter Rose sends | 5 min |
| 2 | If using a CRM, map that field to a rose_session_id property. In HubSpot, Rose provisions the property automatically once OAuth is connected. |
5 min |
| 3 | (Calendly only) Use inline embed method instead of initPopupWidget for on-page detection |
15 min |
See Calendly Integration for detailed setup.
Client effort: Very Low Reliability: High
Embedded form (non-booking)¶
When it applies: The conversion form is embedded on the client's website — HubSpot embedded form, Typeform, Superform, standard HTML <form>, or SPA form.
What Rose does:
1. Listens for PostMessage events from iframe providers (HubSpot, Typeform)
2. Listens for native submit events on HTML forms
3. Detects Superform completion via sf:complete event
4. Falls back to network observation for SPA forms without <form> elements
Supported providers (PostMessage detection):
| Provider | Detection | Submission signal |
|---|---|---|
| HubSpot | hsFormCallback PostMessage |
onFormSubmitted |
| Typeform | form-ready PostMessage |
form-submit |
What the client does: Nothing. This is fully automatic.
With form data (optional): Rose can also intercept the outbound POST request via POST request capture, getting the full form payload. This requires enabling post_request_capture in the client's config.
See Widget Interaction Tracking for all 7 detection strategies.
Client effort: None Reliability: High
Thank-you page redirect¶
When it applies: The client's form redirects to a thank-you or confirmation page on the same site after submission (e.g., /thank-you, /confirmation, /demo-booked). The form can be anything — standard HTML, server-side, SPA — as long as it redirects.
What Rose does:
1. Configures thankyou_page_patterns with URL patterns to match
2. On every page load and SPA navigation, checks if the current URL matches
3. Wraps history.pushState() and history.replaceState() to catch SPA navigations
4. Tracks each matching URL only once per session
What the client does:
| Step | Action | Effort |
|---|---|---|
| 1 | Tell Rose what their thank-you page URL looks like | 1 min |
Limitations: - Event only — Rose only knows the visitor reached the thank-you page, not what they submitted - The visitor must remain on the client's site (external thank-you pages can't be detected) - The Rose widget must be loaded on the thank-you page
Combining with POST request capture: Rose can capture the form payload on the form page, then confirm the conversion via the thank-you page redirect. The rose_last_active_session cookie persists through the redirect.
Client effort: None Reliability: Medium (depends on consistent thank-you page URLs)
HubSpot (with form data)¶
When it applies: The client uses HubSpot and the form is not embedded on the page and has no thank-you page redirect.
Rose gets the conversion with form data by syncing with HubSpot. The client still needs to get the session ID into their HubSpot contact (Axis 1). Rose provisions the HubSpot property group and contact properties during OAuth callback setup, but it does not auto-populate those values.
What Rose does: 1. Provides OAuth connection flow in backoffice 2. Periodically syncs contacts, leads, companies from HubSpot 3. Shows Rose conversation history on HubSpot contact cards
What the client does:
| Step | Action | Effort |
|---|---|---|
| 1 | Connect HubSpot in Rose backoffice (Integrations > HubSpot > Connect) | 2 min |
| 2 | Let Rose provision the Rose group (rose_integration) plus rose_session_id and rose_visitor_id in HubSpot during connection setup |
2 min |
| 3 | Add hidden field to their form to capture session ID | 15 min |
| 4 | Map hidden field to rose_session_id HubSpot property |
5 min |
How the client captures the session ID:
| Visitor action | Where session ID lives | How to capture |
|---|---|---|
| Clicked a Rose CTA | URL parameter ?utm_id=... |
JS reads URL param → hidden field |
| Clicked client's own CTA | rose_last_active_session cookie |
JS reads cookie → hidden field |
<input type="hidden" name="rose_session_id" id="rose_session_id" />
<script>
const urlId = new URLSearchParams(window.location.search).get('utm_id');
const cookieMatch = document.cookie.match(/rose_last_active_session=([^;]+)/);
const cookieId = cookieMatch ? decodeURIComponent(cookieMatch[1]) : null;
const sessionId = urlId || cookieId;
if (sessionId) document.getElementById('rose_session_id').value = sessionId;
</script>
See HubSpot Integration for OAuth setup.
Client effort: Medium Reliability: High
Webhook to Rose (with form data, no HubSpot)¶
When it applies: Last resort — no embedded form, no thank-you page, no HubSpot. The client sends conversion events to Rose via a webhook.
What Rose does: 1. Provides an API endpoint to receive conversion events 2. Matches the session ID to the Rose conversation 3. Updates the visitor record with the conversion data
What the client does:
| Step | Action | Effort |
|---|---|---|
| 1 | Add hidden field to capture session ID (same as HubSpot path) | 15 min |
| 2 | Set up automation (n8n, Zapier, or custom) to POST conversion to Rose | 15 min |
| 3 | Map form fields to Rose webhook payload | 10 min |
Proposed payload (client → Rose):
{
"event_type": "form_conversion",
"session_id": "abc-123-def",
"email": "lead@company.com",
"form_url": "https://client.com/book-demo",
"metadata": {
"company": "Acme Corp",
"company_size": "11-50"
}
}
Client effort: Low-Medium Reliability: High (server-to-server)
Existing outbound webhook (Rose → Client)¶
Rose already sends a post_conversion_complete webhook from Rose to the client when a visitor completes Rose's own post-conversion qualification form. This is the reverse direction — Rose pushes data to the client. It delivers the richest data (AI scoring, all qualification answers, session ID), but only fires when the visitor completes Rose's own form, not the client's form.
Summary¶
| Path | Tier | Give ID (Axis 1) | Get conversion (Axis 2) | Client effort | Status |
|---|---|---|---|---|---|
| Calendly / Cal.com on page | 🔵 Event only | Rose decorates URLs | PostMessage — automatic | Very Low | Done |
| Embedded form | 🔵 Event only | Automatic (cookie) | PostMessage / form events — automatic | None | Done |
| Embedded form + POST capture | 🟢 Form data | Automatic (cookie) | POST capture — automatic | None | Done (discovery) |
| Thank-you page | 🔵 Event only | Cookie persists through redirect | URL pattern match — automatic | None | Done |
| Calendly external link | 🟡 Rose only | Rose decorates URLs | No detection (visitor left) | Very Low | Done |
| Off-site form + HubSpot | 🟡 Rose only | Client adds hidden field | Rose syncs from HubSpot | Medium | Partial |
| Off-site form + webhook | 🟡 Rose only | Client adds hidden field | Client POSTs to Rose | Low-Medium | Needs building |
| Off-site form, no integration | 🔴 Nothing | — | — | — | Dead end |
Provider matrix: what to do for each scenario¶
For each conversion scenario, this matrix shows what Rose does automatically and what the client needs to configure on each axis.
| Scenario | Tier | Axis 1: Session ID | Axis 2: Conversion detection | Client setup |
|---|---|---|---|---|
| Calendly popup/inline on page | 🔵 Event only | Rose decorates URL with UTM — automatic | PostMessage — automatic | Configure hidden field in Calendly to capture UTM. Use inline embed (not initPopupWidget). |
| Calendly popup + POST capture | 🟢 Form data | Rose decorates URL with UTM — automatic | POST capture — automatic | Same as above + enable post_request_capture. |
| Calendly external link | 🟡 Rose only | Rose decorates the link with UTM — automatic | Visitor left the site — no detection | Configure hidden field in Calendly. No conversion detection — need HubSpot or webhook. |
| Cal.com embed on page | 🔵 Event only | Rose decorates URL with UTM — automatic | PostMessage — automatic | Configure hidden field in Cal.com to capture UTM. |
| HubSpot iframe form | 🔵 Event only | Session ID in cookie — automatic | PostMessage — automatic | None. |
| HubSpot iframe + POST capture | 🟢 Form data | Session ID in cookie — automatic | POST capture — automatic | Enable post_request_capture. |
| HubSpot embedded React form | 🔵 Event only | Session ID in cookie — automatic | DOM observation — automatic | None. |
| Typeform embed | 🔵 Event only | Session ID in cookie — automatic | PostMessage — automatic | None. |
| Superform (Webflow) | 🔵 Event only | Session ID in cookie — automatic | sf:complete event — automatic |
None. |
Standard HTML <form> |
🔵 Event only | Session ID in cookie — automatic | Native submit event — automatic |
Configure form_tracking_pages in Rose. |
| Any embedded form + POST capture | 🟢 Form data | Session ID in cookie — automatic | POST capture — automatic | Enable post_request_capture. |
| Thank-you page redirect | 🔵 Event only | Cookie persists through redirect — automatic | URL pattern match — automatic | Tell Rose the thank-you page URL pattern. |
| Off-site form + HubSpot | 🟡 Rose only | Client reads cookie/UTM into hidden field | Rose syncs contacts from HubSpot | Connect HubSpot OAuth so Rose provisions the properties. Add hidden field. Map to rose_session_id. |
| Off-site form + webhook | 🟡 Rose only | Client reads cookie/UTM into hidden field | Client POSTs to Rose endpoint | Set up webhook. Add hidden field. |
| Off-site form, no integration | 🔴 Nothing | — | — | Dead end — add HubSpot or webhook. |
What needs to be built¶
Short-term¶
- Inbound conversion webhook endpoint — Accept conversion events with form data from clients who don't have HubSpot
Medium-term¶
-
POST request capture → conversion attribution — The POST request capture system already captures form payloads. Connecting it to the conversion attribution pipeline would give Rose full form data automatically for embedded forms, without client effort.
-
HubSpot auto-population — Rose now provisions the HubSpot property group and properties during OAuth connection. The remaining work is auto-populating
rose_session_idfrom Rose's tracking data so HubSpot clients no longer need a hidden field.
Nice-to-have¶
- Auto-inject session ID into non-embedded forms — Rose's form detection already knows when a form is submitted. Injecting a hidden
rose_session_idfield before submission would make automatic detection work for ALL forms on the page, not just iframes.