diff --git a/handbook/finance/gtm-architecture.md b/handbook/finance/gtm-architecture.md index c3aafacc92..53aa8205a0 100644 --- a/handbook/finance/gtm-architecture.md +++ b/handbook/finance/gtm-architecture.md @@ -3,6 +3,54 @@ ## Automation +### Capture Eventbrite attendees in Salesforce campaigns + +> _*TL;DR: It's not working, Who should I call and what should I check?*_ +> DRI: @Sampfluger88 +> - Does the Eventbrite page have an "order form" attached? If so, remove it! « This breaks the flow by adding another required form submission not tied to the `New Attendee Registered` action. Attendee name and email will be returned as "Info Requested". +> - Does the SFDC campaign exists? +> - Is the `Event_key` populated correctly on the corresponding SFDC campaign? + +_*Purpose*_ + +Create a reliable, repeatable way to associate Eventbrite registrations with the correct Salesforce contact and campaign. Each event has a unique identifier (`event_key`). We store that identifier on the corresponding Salesforce campaign creating a 1:1 relationship between the published event and the Salesforce campaign. + +This approach “connects” Eventbrite to Salesforce campaigns by using the **`Event_key` as the system-of-record key**. Salesforce Campaigns store that key, and Clay uses it to automatically route registrations to the right Campaign and create/update Campaign Members—cleanly, invisibly, and in a way that can later support additional event platforms. + +_*High-level workflow*_ + +1. A new registration occurs and is captured by Zapier (workflow: [Eventbrite - Event registration » Clay](https://zapier.com/editor/355884186/published)). +2. Zap captures and sends the following info to Clay: + 2. `fullName` + 2. `firstName` + 2. `lastName` + 2. `Email` + 2. `providedNotes`: "`EVENT_NAME` - `EVENT_URL`" + 2. `Event_key`: "Eventbrite-"`EVENT_ID` (This is used to identify the correct Salesforce campaign to add the contact to.) + 2. `campaignMemberStatus`: "Registered" « (Hardcoded) +3. Clay (table: https://app.clay.com/workspaces/315782/workbooks/wb_0t4mlesfmwB8E6W357B/tables/t_0t90w56wNMpfCnCnfFm/views/gv_0t90w56hCPwZrpWtyC6) receives the payload. + 3. The `Event_key` is used to find the correct campaign. + 3. A [historical event](https://fleetdm.com/handbook/finance/gtm-architecture#historical-events-sfdc) gets created with a `relatedCampaign` matching the `Event_key`. Creating a historical event will also create the contact/account if it doesn't already exist. + 3. The name and email is used to pull the correct LinkedIn. If a LinkedIn profile is found, Clay updates the following data in Salesforce: + 3. Job title + 3. Mailing address: (City, State/Province, Country) + 3. Primary buying situation « TODO Document + 3. Role « TODO Document + 3. Sends the following message to the [#help-gitops-workshops](https://fleetdm.slack.com/archives/C0ALY0LJD39) Slack channel. + + ``` + NEW GITOPS REGISTRATION + + _*`fullName`*_ signed up for `proviededNotes` + + - CONTACT: + _*`fullName`*_ (`finalLinkedInProfile`) + `CRMLink` + + - ACCOUNT: + `Rating` - _*`accountName`*_ (`finalLinkedInCompanyUrl`) + ``` + ### LinkedIn comments from tracked posts @@ -19,6 +67,105 @@ We track certian social posts from the [LinkedIn company page](https://www.linke image +## Salesforce + + +### Campaigns (SFDC) + +TODO + +#### For event campaigns (SFDC) + +- **Event platform** (Picklist) – identifies the source platform + - Options: `Eventbrite`, `Luma`, etc. + +- **External event ID** (Text) – stores the platform-specific event identifier + - Example: Eventbrite event ID `123456789` + +- **Event key** (Formula) – composite key for matching integrations + - Formula: `"Event platform"&"-"&"External event ID"` + - Example output: `Eventbrite-123456789` + + +### Historical events (SFDC) + +Historical events (`fleet_website_page_views__c`) is a custom Salesforce object that records timestamped interactions a contact has with Fleet across the website and other channels. Each Historical event record is associated with both a **Contact** and an **Account** in Salesforce, creating a per-contact activity log that the GTM team uses to understand engagement over time. + + +#### What historical events do + +Historical events serve as the single source of truth for tracking how contacts engage with Fleet. Every time a meaningful interaction occurs — whether it's a website page view, a LinkedIn reaction, a newsletter subscription, or a form submission — a Historical event record is created in Salesforce. This gives GTM teams a chronological view of engagement that helps with: + +- Measuring psychological progression of contacts and accounts. +- Prioritizing accounts for [research](https://fleetdm.com/handbook/marketing#research-an-account) and outreach. +- Identifying contacts that would benefit from a [POV conversation](https://fleetdm.com/handbook/company/go-to-market-operations#proof-of-value-pov). + + +#### Historical event types and intent signals + +There are two types of Historical event records: + +| Event type | Description | +|:---|:---| +| **Website page view** | Logged when a signed-in user visits a page on fleetdm.com. Includes the page URL and, when available, the ad attribution that brought them to the site. | +| **Intent signal** | Logged when a contact takes a specific high-value action. | +| **Warm-up action** | Logged when a Fleetie takes a specific high-value action toward a contact. | + +The following intent signals are tracked: + +- Followed the Fleet LinkedIn company page +- LinkedIn comment, share, or reaction +- Fleet channel member in MacAdmins Slack or osquery Slack +- Implemented a trial key +- Signed up for a Fleet event +- Registered for a conference +- Engaged with Fleetie at event +- Attended a Fleet happy hour +- Starred, forked, or contributed to the fleetdm/fleet repo on GitHub +- Subscribed to the Fleet newsletter +- Attended a Fleet training course +- Submitted the "Send a message" form +- Scheduled a "Talk to us" or "Let's get you set up" meeting +- Submitted the "GitOps workshop request" form +- Signed up for a fleetdm.com account +- Requested whitepaper download +- Created a quote for a self-service Fleet Premium license + + +#### How historical events are triggered + +Historical event records are created automatically by the Fleet website backend (`website/api/helpers/salesforce/create-historical-event.js`). The helper is called from several code paths: + +| Trigger | Code path | Event type | +|:---|:---|:---| +| Signed-in user views a page on fleetdm.com | `website/api/hooks/custom/index.js` | Website page view | +| Clay webhook receives LinkedIn activity data | `website/api/controllers/webhooks/receive-from-clay.js` | Intent signal | +| User subscribes to the Fleet newsletter | `website/api/controllers/create-or-update-one-newsletter-subscription.js` | Intent signal | +| User submits the "Send a message" contact form | `website/api/controllers/deliver-contact-form-message.js` | Intent signal | +| User requests a whitepaper download | `website/api/controllers/deliver-whitepaper-download-request.js` | Intent signal | +| User creates a self-service quote | `website/api/controllers/customers/create-quote.js` | Intent signal | +| User submits the "GitOps workshop request" form | `website/api/controllers/deliver-gitops-workshop-request.js` | Intent signal | +| User signs up for a fleetdm.com account | `website/api/controllers/entrance/signup.js` | Intent signal | + +In every case, the website first calls `updateOrCreateContactAndAccount` to ensure the contact and account exist in Salesforce, then calls `createHistoricalEvent` with the returned `salesforceContactId` and `salesforceAccountId`. + + +#### Historical event fields + +| Salesforce field API name | Description | +|:---|:---| +| `Contact__c` | Lookup to the related Contact record. | +| `Account__c` | Lookup to the related Account record. | +| `Event_type__c` | The type of event: "Website page view" or "Intent signal". | +| `Intent_signal__c` | The specific intent signal (only for Intent signal events). | +| `Content__c` | Free-text content associated with the event (e.g. a LinkedIn comment or form message). | +| `Content_url__c` | URL of the content (e.g. a LinkedIn post URL). | +| `Interactor_profile_url__c` | The LinkedIn profile URL of the person who interacted. | +| `Page_URL__c` | The fleetdm.com page URL (only for Website page view events). | +| `Website_visit_reason__c` | Ad attribution string, if the user arrived via an ad within the last 30 minutes. | +| `Related_campaign__c` | Related Salesforce campaign, if applicable. | + +> Historical event records are only created in the production environment. When deleting a contact's data (e.g. for a data deletion request), any related Historical event records associated with that contact are also automaticly deleted. diff --git a/handbook/marketing/event-execution.md b/handbook/marketing/event-execution.md index c4ee18b0bc..3ecab1b533 100644 --- a/handbook/marketing/event-execution.md +++ b/handbook/marketing/event-execution.md @@ -735,106 +735,6 @@ create_sub_issue "6. Post-Mortem & Follow-Up" echo "Done." ``` -## **Connecting Eventbrite registrations to Salesforce campaigns (event ID key)** - -#### **Purpose** - -We need a reliable, repeatable way to associate each Eventbrite registration with the correct Salesforce Campaign **without adding any visible fields to the attendee experience**. This approach uses the Eventbrite **Event ID** as the canonical key to map registrations to Campaigns in Salesforce. - -#### **Core idea** - -Each Eventbrite event has a unique identifier (`event_id`). We store that identifier on the corresponding Salesforce Campaign. When a new registration occurs, our integration (e.g., Clay) reads the `event_id` from the registration payload, finds the matching Campaign, then creates/updates the Campaign Member. - -This creates a clean 1:1 relationship: - -**1 Eventbrite Event → 1 Salesforce Campaign → Many Campaign Members (registrants)** - -#### **Why this approach** - -* **Invisible to attendees:** No hidden checkout questions or user-facing “tags.” -* **Stable and unambiguous:** Event IDs are unique and don’t depend on event names. -* **Easy to operationalize:** Simple to document and enforce as a process. -* **Scalable to other platforms:** The same pattern could be extended to Lu.ma later using a platform-specific ID or key (if we ever want to use Lu.ma) - -### **Data model (Salesforce)** - -#### **Campaign fields** - -Add the following fields to **Campaign**: - -* **Event platform** (Picklist) – identifies the source platform - * Options: `Eventbrite`, `Luma`, etc. - -* **External event ID** (Text) – stores the platform-specific event identifier - * Example: Eventbrite event ID `123456789` - -* **Event key** (Formula) – composite key for matching integrations - * Formula: `"Event platform"&"-"&"External event ID"` - * Example output: `Eventbrite-123456789` - -The composite key pattern lets us use one matching field across platforms and avoid collisions if we ever want to use [Lu.ma](http://Lu.ma) or others. - - -### **Operational workflow** - -##### **1\) Capture the Eventbrite event ID** - -The Event ID can be sourced from: - -* Eventbrite event page URL (contains the ID), or -* Event settings / Event details in Eventbrite, or -* Eventbrite API / integration payload - -**Important:** Do not use event name as a key (names can change and are not guaranteed unique). - -##### **2\) Create the Salesforce Campaign** - -* Create a Campaign for the event. -* Set: - * **Event platform**: `Eventbrite` (or the appropriate platform) - * **External event ID**: `{event_id}` (e.g., `123456789`) - * **Event key**: Auto-populated by formula (no manual entry needed) - -##### **3\) Integration logic (Clay)** - -When Clay receives a new Eventbrite registration/attendee record: - -1. **Extract** `event_id` from the Eventbrite payload - * Clay matches the `Event key` from Zapier to the corresponding campaign. -2. **Find Campaign** in Salesforce where: - * *`Event key = Eventbrite-{event_id}`* -3. **Create/update Person Record** - * Match/Create the Contact -4. **Create/Update Campaign Member** - * Add the person as a Campaign Member on the matched Campaign - * Optionally set Campaign Member Status (e.g., `Registered`, `Attended`, `No Show`) if we later sync those states - - -### **Assumptions / scope** -* **One ticket type per Campaign** (i.e., we do not need ticket-type-level mapping). -* **One event maps to exactly one Salesforce Campaign**. -* We are focusing on **registrations** (Campaign Members). - -### **Governance & quality controls** -To keep the system clean and prevent broken mappings: - -* **Required fields:** Ensure Campaigns intended for Eventbrite syncing have `Event Key` populated. -* **Uniqueness guardrails:** Prevent multiple Campaigns from sharing the same Event Key (via process, reporting, or validation rules). -* **Monitoring:** Have a Clay/Salesforce report for “registrations received with no matching Campaign” to catch missing IDs early. - -### **(FUTURE) Extending this to Lu.ma (future)** - -If we adopt Lu.ma later, we can follow the same model: - -* Set **Event platform** to `Luma` -* Set **External event ID** to Lu.ma’s stable event identifier (ID or slug) -* **Event key** formula automatically generates the composite key: `Luma-{external_event_id}` -* Clay uses this Event key to map inbound registrations to the correct Campaign -* No attendee-visible fields required. - -### **Summary** - -This approach “connects” Eventbrite to Salesforce Campaigns by using the **Eventbrite Event ID as the system-of-record key**. Salesforce Campaigns store that key, and Clay uses it to automatically route registrations to the right Campaign and create/update Campaign Members—cleanly, invisibly, and in a way that can later support additional event platforms.