Skip to content

Conversion Attribution

Goal

Rose needs two things to attribute a conversion to a conversation:

  1. Give the session ID to the client — so the client's CRM knows which Rose conversation led to this conversion
  2. 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?

flowchart TD A[Give ID to client] --> B{Visitor clicks a Rose CTA?} B -->|Yes| C[UTM param in URL — automatic] C --> G[Client: hidden field reads UTM from URL] B -->|No — client's own button| D{Booking tool on the page? Calendly, Cal.com} D -->|Yes| E[Rose decorates booking links — automatic] E --> H[Client: hidden field in booking tool] D -->|No| F[Session ID in cookie — client reads it] F --> I[Client: hidden field reads cookie] style C fill:#d4edda,stroke:#28a745 style E fill:#d4edda,stroke:#28a745 style F fill:#cce5ff,stroke:#004085 style G fill:#f5f5f5,stroke:#999 style H fill:#f5f5f5,stroke:#999 style I fill:#f5f5f5,stroke:#999

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.

flowchart TD A[Get conversion back to Rose] --> B{Conversion happens on the page?} B -->|Yes — form, booking overlay, iframe| C[Rose detects it automatically] C --> D{Need the actual form fields?} D -->|No, just attribution| E[Rose knows it converted — no form content] D -->|Yes| F[Rose intercepts the POST and gets the fields] B -->|No| G{Thank-you page redirect?} G -->|Yes| H[Rose detects the redirect — no form content] G -->|No| X{Session ID reaches the client? — see Axis 1} X -->|No| Y[Dead end — solve Axis 1 first] X -->|Yes| I{Client has HubSpot?} I -->|Yes| J[Rose syncs contact data from HubSpot] J --> K[Client: connect OAuth] I -->|No| L[Client sends form data via webhook] L --> M[Client: set up webhook] style E fill:#cce5ff,stroke:#004085 style F fill:#d4edda,stroke:#28a745 style H fill:#cce5ff,stroke:#004085 style Y fill:#f8d7da,stroke:#dc3545 style J fill:#fff3cd,stroke:#856404 style L fill:#fff3cd,stroke:#856404 style K fill:#f5f5f5,stroke:#999 style M fill:#f5f5f5,stroke:#999

"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

  1. Inbound conversion webhook endpoint — Accept conversion events with form data from clients who don't have HubSpot

Medium-term

  1. 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.

  2. HubSpot auto-population — Rose now provisions the HubSpot property group and properties during OAuth connection. The remaining work is auto-populating rose_session_id from Rose's tracking data so HubSpot clients no longer need a hidden field.

Nice-to-have

  1. Auto-inject session ID into non-embedded forms — Rose's form detection already knows when a form is submitted. Injecting a hidden rose_session_id field before submission would make automatic detection work for ALL forms on the page, not just iframes.