Capture eventbrite attendees in SFDC campaigns (#42788)

- Add documentation for historical events and salesforce campaigns.
- Moved and consolidated "Connecting Eventbrite registrations to
Salesforce campaigns (event ID key)"
This commit is contained in:
Sam Pfluger
2026-03-31 21:15:47 -05:00
committed by GitHub
parent 5a96675056
commit c62c39ff2f
2 changed files with 147 additions and 100 deletions
+147
View File
@@ -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
<img width="1410" height="1174" alt="image" src="https://github.com/user-attachments/assets/da2dccaa-e5ac-4373-9d93-d02b2a1bd8cd" />
## 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.
-100
View File
@@ -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 dont 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.mas 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.
<meta name="maintainedBy" value="johnjeremiah">