# Campaign attribution to CRM: webhook setup for Salesforce and HubSpot **Author:** SkipUp Team **Date:** 2026-03-14 **Category:** Integrations **Tags:** Campaign Attribution, CRM Integration, Webhook, Salesforce, HubSpot, Zapier, Make, n8n, RevOps, Campaign Lead Conversion Map SkipUp's meeting webhook to Salesforce Campaign Members or HubSpot Deals for campaign attribution. Step-by-step Zapier recipes with field mappings. > Step-by-step webhook integration tutorial mapping SkipUp's meeting_booked event (also called booking notification or HTTP callback) to Salesforce Campaign Member records and HubSpot Deal objects with campaign attribution. Includes representative JSON payload schema with field descriptions, Zapier Catch Hook recipes for both CRMs, Make and n8n quick-start alternatives, field mapping tables, and a troubleshooting guide covering duplicate contacts, campaign lookup failures, and HubSpot association order. Web version: https://blog.skipup.ai/campaign-attribution-crm-webhook-integration --- > **TL;DR:** SkipUp fires a webhook on every campaign meeting booking. That webhook carries the campaign source, attendee, meeting time, and assigned rep — everything your CRM needs to attribute the meeting to the right campaign. This tutorial walks through catching that webhook with Zapier and mapping it to Salesforce Campaign Members or HubSpot Deals. No native CRM connector required. No custom code. The same approach works with Make or n8n if Zapier is not your middleware of choice. > **Key Facts:** > - SkipUp sends a `meeting_booked` webhook (also called an event notification or HTTP callback) containing campaign source, attendee details, meeting time, and assigned rep for every campaign meeting booked through a campaign email address. > - CRM sync is webhook-based — SkipUp does not write directly to Salesforce or HubSpot APIs. Middleware (Zapier, Make, or n8n) catches the webhook and maps fields to CRM objects. > - For Salesforce, the target object is a CampaignMember record linked to the source Campaign with status "Meeting Booked." For HubSpot, the target is a Deal associated with the Contact and a Meeting engagement logged on the Contact timeline. > - Campaign attribution through webhooks is single-touch: each booked meeting attributes to the campaign email address the prospect responded to. This is the same [email-based attribution](/campaign-attribution-without-utms) model used across all SkipUp campaigns. > - Prerequisites: SkipUp account with webhooks enabled, Zapier Starter plan or higher (or Make/n8n equivalent), Salesforce Enterprise Edition or higher (API access requires Enterprise), HubSpot Sales Hub Professional or higher. --- ## What does the webhook payload contain? Every campaign meeting booked through SkipUp triggers a `meeting_booked` event sent to your configured webhook URL. Campaign attribution starts here: the payload carries the fields your CRM needs to create an attributed record. Representative payload: ```json { "event": "meeting_booked", "campaign_id": "camp_abc123", "campaign_name": "NAA 2026", "attendee_email": "sarah.chen@prospect.com", "attendee_name": "Sarah Chen", "meeting_time": "2026-03-15T14:00:00Z", "assigned_rep_email": "marcus@acme.com", "assigned_rep_name": "Marcus Rivera", "meeting_type": "campaign_meeting", "booking_source": "email" } ``` *This is a representative payload. Check [SkipUp's API documentation](https://docs.skipup.ai) for current field names and additional fields.* The `campaign_id` field is the mapping anchor. Use it — not `campaign_name` — when looking up CRM Campaign records. Display names with trailing spaces or special characters cause silent lookup failures. Internal IDs do not. Every field in this payload flows downstream into your CRM. The sections below show exactly where each field lands in Salesforce and HubSpot. --- ## How do you map the webhook to Salesforce Campaign Members? The goal is a CampaignMember record in Salesforce that links the prospect to the source campaign with a status of "Meeting Booked." Four Zapier actions get you there. **Trigger**: Webhooks by Zapier → Catch Hook. This creates a unique URL. Register that URL in SkipUp's webhook settings for the `meeting_booked` event. No native SkipUp Zapier app trigger is needed — the generic Catch Hook receives any HTTP POST. **Action 1: Find the Contact.** Salesforce → Find Record. Search the Contact object by Email = `attendee_email`. If a match exists, you have the ContactId for the CampaignMember record. **Action 2: Create a Lead if needed.** Salesforce → Create Record (conditional, runs only if Action 1 returns no match). Object: Lead. Map `attendee_email` to Email. The `attendee_name` field arrives as a single string, but Salesforce requires separate FirstName and LastName fields. Add a Formatter by Zapier → Split Text step: split on space, map the first segment to FirstName and the last segment to LastName. Set LeadSource to "Campaign Email." **Action 3: Find the Salesforce Campaign.** Salesforce → Find Record. Object: Campaign. SkipUp's `campaign_id` is an internal identifier, not a Salesforce CampaignId. This step resolves the mapping. Two approaches work: match on a custom External ID field (`SkipUp_Campaign_ID__c` = `campaign_id`) for reliable, case-insensitive lookups, or match on Campaign Name = `campaign_name` if you prefer simplicity over resilience. The External ID approach is recommended. The output is the Salesforce CampaignId used in Action 4. **Action 4: Create the CampaignMember.** Salesforce → Create Record. Object: CampaignMember. Map ContactId (or LeadId from Action 2) and CampaignId from Action 3. Set Status to "Meeting Booked." If "Meeting Booked" does not exist in your CampaignMemberStatus picklist, add it in Salesforce Setup → Campaign Member Statuses before running the Zap. Priya runs RevOps at a mid-market SaaS company with eight active campaigns across trade shows, webinars, and partner referrals. Before this integration, her reps manually tagged Campaign Members in Salesforce after each meeting — when they remembered. Compliance hovered around 40%. The `campaign_id` field in SkipUp's webhook resolved the consistency problem. Every booked meeting now creates a CampaignMember automatically, and her [campaign analytics dashboard](/campaign-analytics-meetings-booked) shows booking rates per campaign without relying on rep data entry. **Field mapping reference:** | Webhook field | Salesforce object | Salesforce field | Notes | |---|---|---|---| | `campaign_id` | Campaign (via lookup) | `SkipUp_Campaign_ID__c` (custom External ID) | Look up Campaign in Action 3; do not map directly | | `attendee_email` | Contact or Lead | Email | Find-or-create pattern | | `attendee_name` | Contact or Lead | FirstName, LastName | Parse with Formatter → Split Text | | `assigned_rep_email` | Contact or Lead | OwnerId | Optional: look up User by email | | `meeting_time` | Event (optional) | StartDateTime | Optional: log as activity | --- The same webhook data maps to HubSpot, though the CRM objects differ: HubSpot uses Deal properties and Contact associations where Salesforce uses purpose-built CampaignMember records. The field mappings below show the HubSpot-specific path. ## How do you map the webhook to HubSpot Deals for campaign attribution? In HubSpot, the attribution path runs through Deals associated with Contacts. The webhook creates a Deal tied to the campaign source and logs a Meeting engagement on the Contact timeline. Before running this Zap, create the custom properties it references: Lead Source on the Contact object and Campaign Name and Campaign ID on the Deal object. Both are created in HubSpot Settings → Properties. **Trigger**: Same Webhooks by Zapier Catch Hook URL. One webhook endpoint can feed both CRMs if you use Zapier Paths (available on any paid plan), or use separate Zaps. **Action 1: Find the Contact.** HubSpot → Find Contact by `attendee_email`. **Action 2: Create or update the Contact.** HubSpot → Create or Update Contact. Use "Create or Update" (not bare "Create") to avoid duplicate records when the same prospect books multiple campaign meetings. Map `attendee_email`, parse `attendee_name` into First Name and Last Name fields using Formatter → Split Text (same approach as the Salesforce recipe), and set a custom Lead Source property to the `campaign_name`. For the reverse use case (triggering SkipUp meetings from HubSpot form submissions), see [automate meeting scheduling from HubSpot form submissions](/automate-meeting-scheduling-hubspot-form-submissions). **Action 3: Create a Deal.** HubSpot → Create Deal. Map Deal Name to a combination of attendee name and campaign name. Set the pipeline stage to "Meeting Booked" and use the "Associate Deal With" field to link the Contact ID from Action 2. HubSpot requires the Contact ID for association — the Contact must exist before the Deal can reference it. Map `campaign_name` and `campaign_id` to custom Deal properties for campaign attribution reporting. **Action 4: Log the Meeting.** HubSpot → Create Engagement (type: Meeting). Map `meeting_time` to the engagement timestamp and associate with the Contact. This surfaces the booked meeting on the Contact's activity timeline, giving sales reps context before the call. This action requires HubSpot Sales Hub Professional or higher. If your plan does not include Meeting engagements, skip this action — the Deal from Action 3 still provides campaign attribution. **Field mapping reference:** | Webhook field | HubSpot object | HubSpot property | Notes | |---|---|---|---| | `attendee_email` | Contact | Email (built-in) | Find-or-create in Actions 1–2 | | `attendee_name` | Contact | First Name, Last Name (built-in) | Parse with Formatter → Split Text | | `campaign_name` | Contact | Lead Source (custom property) | Create in HubSpot Settings if not present | | `campaign_name` | Deal | Campaign Name (custom property) | For campaign attribution reporting | | `campaign_id` | Deal | Campaign ID (custom property) | For programmatic lookups | | `meeting_time` | Engagement (Meeting) | Timestamp (built-in) | ISO 8601 format | | `assigned_rep_email` | Deal | Deal Owner (built-in) | Optional: look up HubSpot User by email | Teams running [partner referral programs](/partner-referral-lead-tracking) can use the identical webhook-to-Deal pattern. The `campaign_id` field works the same way whether the campaign source is a trade show, a webinar, or a partner email address. Attribution flows through Deal properties and Contact associations, which means reporting in HubSpot rolls up naturally through deal pipelines and contact lists. --- ## What if you use Make or n8n instead of Zapier? The webhook is a standard HTTP POST. Any middleware that can receive an HTTP request and call CRM APIs can replace Zapier. For a general guide to Zapier integration with SkipUp, see [automate meeting scheduling with Zapier](/automate-meeting-scheduling-with-zapier). The recipes below adapt the same campaign attribution pattern for alternative platforms. **Make** uses a Custom Webhook module as the trigger — functionally equivalent to Zapier's Catch Hook. Salesforce and HubSpot modules map the same fields to the same CRM objects. Make's visual router adds one capability not available in Zapier's base paid tier: conditional routing. A single webhook can route to Salesforce or HubSpot based on a field value, which matters if your organization runs both CRMs across different business units. Make charges by operation count rather than per-task, which can reduce cost at high webhook volumes. **n8n** uses a Webhook trigger node with the same POST reception. Salesforce and HubSpot nodes provide equivalent actions. n8n can be self-hosted (Docker or npm) or used via n8n Cloud. Self-hosted carries no per-execution fees and gives full control over error handling and retry logic. n8n Cloud uses usage-based pricing with a managed hosting layer. For teams processing hundreds of campaign bookings monthly, either option avoids the per-task cost structure of Zapier and Make. Both alternatives follow the same pattern: catch the webhook, find or create the contact, create the attributed CRM record. The middleware changes. The field mappings do not. --- ## How do you verify campaign attribution is flowing to your CRM? Seven failure modes cover the majority of webhook-to-CRM integration issues. Most surface during the first test booking, not in production. **Webhook not firing**: SkipUp's webhook settings must have the Catch Hook URL saved and the `meeting_booked` event enabled. Test by creating a campaign booking and checking the Zapier task history for the incoming payload. **Authentication expired**: Zapier and Make CRM connections require periodic re-authentication. Salesforce OAuth tokens expire based on your org's session settings. Set a calendar reminder for 90-day re-auth cycles — a missed re-auth silently drops every booking until someone notices the CRM gap. **Duplicate contacts**: Always use a "Find or Create" pattern. A bare "Create Contact" action generates duplicates when the same prospect books through multiple campaigns. The find step costs one additional API call per webhook: a negligible price for data integrity. **Campaign lookup returns no results**: If you use the External ID approach for Salesforce, verify that `SkipUp_Campaign_ID__c` is populated on the Campaign record. If you match by Campaign Name, verify the name matches exactly — trailing spaces, ampersands, or renamed campaigns break the lookup silently. In both cases, test with a known campaign before going live. **HubSpot custom properties missing**: The Zap will fail on the first run if the custom properties (Lead Source on Contact, Campaign Name and Campaign ID on Deal) do not exist in HubSpot. Create them in HubSpot Settings → Properties before enabling the Zap. **HubSpot association order**: Deals require an existing Contact ID for association. If your Zap creates the Deal before the Contact exists, the association fails. Structure your Zap so Contact creation always precedes Deal creation. **Field mapping case sensitivity**: Webhook field names are case-sensitive. `attendee_email` is not `Attendee_Email`. Copy field names directly from the webhook payload — do not retype them. Start with one campaign. Register the Catch Hook URL, trigger a test booking, and verify the CampaignMember (Salesforce) or Deal (HubSpot) appears with the correct campaign attribution, attendee data, and status. Once the first record flows through, extend to additional campaigns. From that point forward, the webhook fires on every [campaign lead conversion](/campaign-lead-conversion), the middleware maps the fields, and the CRM record appears automatically.